<commit_before>/* global Vulcan */
import Users from 'meteor/vulcan:users';
import { editMutation, getCollection, Utils } from 'meteor/vulcan:core';
import { Revisions } from '../../lib/index';
import { editableCollectionsFields } from '../../lib/editor/make_editable'
import ReadStatuses from '../../lib/collections/readStatus/collection';
import { Votes } from '../../lib/collections/votes/index';
import { Conversations } from '../../lib/collections/conversations/collection'
import sumBy from 'lodash/sumBy';

const transferOwnership = async ({documentId, targetUserId, collection, fieldName = "userId"}) => {
  await editMutation({
    collection,
    documentId,
    set: {[fieldName]: targetUserId},
    unset: {},
    validate: false,
  })
}

const transferCollection = async ({sourceUserId, targetUserId, collectionName, fieldName = "userId"}) => {
  const collection = getCollection(collectionName)
  const documents = await collection.find({[fieldName]: sourceUserId}).fetch()
  // eslint-disable-next-line no-console
  console.log(`Transferring ${documents.length} documents in collection ${collectionName}`)
  for (const doc of documents) {
    await transferOwnership({documentId: doc._id, targetUserId, collection, fieldName})
    // Transfer ownership of all revisions and denormalized references for editable fields
    const editableFieldNames = editableCollectionsFields[collectionName]
    if (editableFieldNames?.length) {
      editableFieldNames.forEach((editableFieldName) => {
        transferEditableField({documentId: doc._id, targetUserId, collection, fieldName: editableFieldName})
      })
    }
  }
  documents.forEach((doc) => {

  })
}

const transferEditableField = ({documentId, targetUserId, collection, fieldName = "contents"}) => {
  // Update the denormalized revision on the document
  editMutation({
    collection,
    documentId,
    set: {[`${fieldName}.userId`]: targetUserId},
    unset: {},
    validate: false
  })
  // Update the revisions themselves
  Revisions.update({ documentId, fieldName }, {$set: {userId: targetUserId}}, { multi: true })
}

const mergeReadStatusForPost = ({sourceUserId, targetUserId, postId}) => {
  const sourceUserStatus = ReadStatuses.findOne({userId: sourceUserId, postId})
  const targetUserStatus = ReadStatuses.findOne({userId: targetUserId, postId})
  const readStatus = new Date(sourceUserStatus?.lastUpdated) > new Date(targetUserStatus?.lastUpdated) ? sourceUserStatus?.isRead : targetUserStatus?.isRead
  const lastUpdated = new Date(sourceUserStatus?.lastUpdated) > new Date(targetUserStatus?.lastUpdated) ? sourceUserStatus?.lastUpdated : targetUserStatus?.lastUpdated
  if (targetUserStatus) {
    ReadStatuses.update({_id: targetUserStatus._id}, {$set: {isRead: readStatus, lastUpdated}})
  } else if (sourceUserStatus) {
    // eslint-disable-next-line no-unused-vars
    const {_id, ...sourceUserStatusWithoutId} = sourceUserStatus
    ReadStatuses.insert({...sourceUserStatusWithoutId, userId: targetUserId})
  }
}

Vulcan.mergeAccounts = async (sourceUserId, targetUserId) => {
  const sourceUser = Users.findOne({_id: sourceUserId})
  const targetUser = Users.findOne({_id: targetUserId})

  // Transfer posts
  await transferCollection({sourceUserId, targetUserId, collectionName: "Posts"})

  // Transfer comments
  await transferCollection({sourceUserId, targetUserId, collectionName: "Comments"})

  // Transfer conversations
  await Conversations.update({participantIds: sourceUserId}, {$set: {"participantIds.$": targetUserId}}, { multi: true })

  // Transfer private messages
  await transferCollection({sourceUserId, targetUserId, collectionName: "Messages"})

  // Transfer notifications
  await transferCollection({sourceUserId, targetUserId, collectionName: "Notifications"})

  // Transfer readStatuses
  const readStatuses = await ReadStatuses.find({userId: sourceUserId}).fetch()
  const readPostIds = readStatuses.map((status) => status.postId)
  readPostIds.forEach((postId) => {
    mergeReadStatusForPost({sourceUserId, targetUserId, postId})
  })

  // Transfer sequences
  await transferCollection({sourceUserId, targetUserId, collectionName: "Sequences"})
  await transferCollection({sourceUserId, targetUserId, collectionName: "Collections"})

  // Transfer localgroups
  await transferCollection({sourceUserId, targetUserId, collectionName: "Localgroups"})

  // Transfer karma
  // eslint-disable-next-line no-console
  console.log("Transferring karma")
  const newKarma = await recomputeKarma(targetUserId)
  await editMutation({
    collection: Users,
    documentId: targetUserId,
    set: {
      karma: newKarma,
      // We only recalculate the karma for non-af karma, because recalculating
      // af karma is a lot more complicated
      afKarma: sourceUser.afKarma + targetUser.afKaram
    },
    validate: false
  })

  // Transfer votes that target content from source user (authorId)
  // eslint-disable-next-line no-console
  console.log("Transferring votes that target source user")
  await Votes.update({authorId: sourceUserId}, {$set: {authorId: targetUserId}}, {multi: true})

  // Transfer votes cast by source user
  // eslint-disable-next-line no-console
  console.log("Transferring votes cast by source user")
  await Votes.update({userId: sourceUserId}, {$set: {userId: targetUserId}}, {multi: true})

  // Change slug of source account by appending "old" and reset oldSlugs array
  // eslint-disable-next-line no-console
  console.log("Change slugs of source account")
  await Users.update({_id: sourceUserId}, {slug: Utils.getUnusedSlug(Users, `${sourceUser.slug}-old`, true)})

  // Add slug to oldSlugs array of target account
  const newOldSlugs = [
    ...(targetUser.oldSlugs || []),
    ...(sourceUser.oldSlugs || []),
    sourceUser.slug
  ]
  // eslint-disable-next-line no-console
  console.log("Changing slugs of target account", sourceUser.slug, newOldSlugs)

  await editMutation({
    collection: Users,
    documentId: targetUserId,
    set: {oldSlugs: newOldSlugs},
    validate: false
  })

  // Mark old acccount as deleted
  // eslint-disable-next-line no-console
  console.log("Marking old account as deleted")
  await editMutation({
    collection: Users,
    documentId: sourceUserId,
    set: {deleted: true},
    validate: false
  })
}

async function recomputeKarma(userId) {
  const user = await Users.findOne({_id: userId})
  if (!user) throw Error("Can't find user")
  const allTargetVotes = await Votes.find({
    authorId: user._id,
    userId: {$ne: user._id},
    legacy: {$ne: true},
    cancelled: false
  }).fetch()
  const totalNonLegacyKarma = sumBy(allTargetVotes, vote => {
    return vote.power
  })
  const totalKarma = totalNonLegacyKarma + (user.legacyKarma || 0)
  return totalKarma
}

Vulcan.getTotalKarmaForUser = recomputeKarma<commit_msg>fix lint<commit_after>/* global Vulcan */
import Users from 'meteor/vulcan:users';
import { editMutation, getCollection, Utils } from 'meteor/vulcan:core';
import { Revisions } from '../../lib/index';
import { editableCollectionsFields } from '../../lib/editor/make_editable'
import ReadStatuses from '../../lib/collections/readStatus/collection';
import { Votes } from '../../lib/collections/votes/index';
import { Conversations } from '../../lib/collections/conversations/collection'
import sumBy from 'lodash/sumBy';

const transferOwnership = async ({documentId, targetUserId, collection, fieldName = "userId"}) => {
  await editMutation({
    collection,
    documentId,
    set: {[fieldName]: targetUserId},
    unset: {},
    validate: false,
  })
}

const transferCollection = async ({sourceUserId, targetUserId, collectionName, fieldName = "userId"}) => {
  const collection = getCollection(collectionName)
  const documents = await collection.find({[fieldName]: sourceUserId}).fetch()
  // eslint-disable-next-line no-console
  console.log(`Transferring ${documents.length} documents in collection ${collectionName}`)
  for (const doc of documents) {
    await transferOwnership({documentId: doc._id, targetUserId, collection, fieldName})
    // Transfer ownership of all revisions and denormalized references for editable fields
    const editableFieldNames = editableCollectionsFields[collectionName]
    if (editableFieldNames?.length) {
      editableFieldNames.forEach((editableFieldName) => {
        transferEditableField({documentId: doc._id, targetUserId, collection, fieldName: editableFieldName})
      })
    }
  }
  documents.forEach((doc) => {

  })
}

const transferEditableField = ({documentId, targetUserId, collection, fieldName = "contents"}) => {
  // Update the denormalized revision on the document
  editMutation({
    collection,
    documentId,
    set: {[`${fieldName}.userId`]: targetUserId},
    unset: {},
    validate: false
  })
  // Update the revisions themselves
  Revisions.update({ documentId, fieldName }, {$set: {userId: targetUserId}}, { multi: true })
}

const mergeReadStatusForPost = ({sourceUserId, targetUserId, postId}) => {
  const sourceUserStatus = ReadStatuses.findOne({userId: sourceUserId, postId})
  const targetUserStatus = ReadStatuses.findOne({userId: targetUserId, postId})
  const readStatus = new Date(sourceUserStatus?.lastUpdated) > new Date(targetUserStatus?.lastUpdated) ? sourceUserStatus?.isRead : targetUserStatus?.isRead
  const lastUpdated = new Date(sourceUserStatus?.lastUpdated) > new Date(targetUserStatus?.lastUpdated) ? sourceUserStatus?.lastUpdated : targetUserStatus?.lastUpdated
  if (targetUserStatus) {
    ReadStatuses.update({_id: targetUserStatus._id}, {$set: {isRead: readStatus, lastUpdated}})
  } else if (sourceUserStatus) {
    // eslint-disable-next-line no-unused-vars
    const {_id, ...sourceUserStatusWithoutId} = sourceUserStatus
    ReadStatuses.insert({...sourceUserStatusWithoutId, userId: targetUserId})
  }
}

Vulcan.mergeAccounts = async (sourceUserId, targetUserId) => {
  const sourceUser = Users.findOne({_id: sourceUserId})
  const targetUser = Users.findOne({_id: targetUserId})

  // Transfer posts
  await transferCollection({sourceUserId, targetUserId, collectionName: "Posts"})

  // Transfer comments
  await transferCollection({sourceUserId, targetUserId, collectionName: "Comments"})

  // Transfer conversations
  await Conversations.update({participantIds: sourceUserId}, {$set: {"participantIds.$": targetUserId}}, { multi: true })

  // Transfer private messages
  await transferCollection({sourceUserId, targetUserId, collectionName: "Messages"})

  // Transfer notifications
  await transferCollection({sourceUserId, targetUserId, collectionName: "Notifications"})

  // Transfer readStatuses
  const readStatuses = await ReadStatuses.find({userId: sourceUserId}).fetch()
  const readPostIds = readStatuses.map((status) => status.postId)
  readPostIds.forEach((postId) => {
    mergeReadStatusForPost({sourceUserId, targetUserId, postId})
  })

  // Transfer sequences
  await transferCollection({sourceUserId, targetUserId, collectionName: "Sequences"})
  await transferCollection({sourceUserId, targetUserId, collectionName: "Collections"})

  // Transfer localgroups
  await transferCollection({sourceUserId, targetUserId, collectionName: "Localgroups"})

  // Transfer karma
  // eslint-disable-next-line no-console
  console.log("Transferring karma")
  const newKarma = await recomputeKarma(targetUserId)
  await editMutation({
    collection: Users,
    documentId: targetUserId,
    set: {
      karma: newKarma,
      // We only recalculate the karma for non-af karma, because recalculating
      // af karma is a lot more complicated
      afKarma: sourceUser.afKarma + targetUser.afKaram
    },
    validate: false
  })

  // Transfer votes that target content from source user (authorId)
  // eslint-disable-next-line no-console
  console.log("Transferring votes that target source user")
  await Votes.update({authorId: sourceUserId}, {$set: {authorId: targetUserId}}, {multi: true})

  // Transfer votes cast by source user
  // eslint-disable-next-line no-console
  console.log("Transferring votes cast by source user")
  await Votes.update({userId: sourceUserId}, {$set: {userId: targetUserId}}, {multi: true})

  // Change slug of source account by appending "old" and reset oldSlugs array
  // eslint-disable-next-line no-console
  console.log("Change slugs of source account")
  await Users.update({_id: sourceUserId}, {slug: Utils.getUnusedSlug(Users, `${sourceUser.slug}-old`, true)})

  // Add slug to oldSlugs array of target account
  const newOldSlugs = [
    ...(targetUser.oldSlugs || []),
    ...(sourceUser.oldSlugs || []),
    sourceUser.slug
  ]
  // eslint-disable-next-line no-console
  console.log("Changing slugs of target account", sourceUser.slug, newOldSlugs)

  await editMutation({
    collection: Users,
    documentId: targetUserId,
    set: {oldSlugs: newOldSlugs},
    validate: false
  })

  // Mark old acccount as deleted
  // eslint-disable-next-line no-console
  console.log("Marking old account as deleted")
  await editMutation({
    collection: Users,
    documentId: sourceUserId,
    set: {deleted: true},
    validate: false
  })
}

async function recomputeKarma(userId) {
  const user = await Users.findOne({_id: userId})
  if (!user) throw Error("Can't find user")
  const allTargetVotes = await Votes.find({
    authorId: user._id,
    userId: {$ne: user._id},
    legacy: {$ne: true},
    cancelled: false
  }).fetch()
  const totalNonLegacyKarma = sumBy(allTargetVotes, vote => {
    return vote.power
  })
  const totalKarma = totalNonLegacyKarma + (user.legacyKarma || 0)
  return totalKarma
}

Vulcan.getTotalKarmaForUser = recomputeKarma
<|endoftext|>


<commit_before><commit_msg>fix naming convention<commit_after><|endoftext|>


<commit_before>Meteor._SUPPORTED_DDP_VERSIONS = [ 'pre1' ];

Meteor._MethodInvocation = function (options) {
  var self = this;

  // true if we're running not the actual method, but a stub (that is,
  // if we're on a client (which may be a browser, or in the future a
  // server connecting to another server) and presently running a
  // simulation of a server-side method for latency compensation
  // purposes). not currently true except in a client such as a browser,
  // since there's usually no point in running stubs unless you have a
  // zero-latency connection to the user.
  this.isSimulation = options.isSimulation;

  // XXX Backwards compatibility only. Remove this before 1.0.
  this.is_simulation = this.isSimulation;

  // call this function to allow other method invocations (from the
  // same client) to continue running without waiting for this one to
  // complete.
  this._unblock = options.unblock || function () {};
  this._calledUnblock = false;

  // current user id
  this.userId = options.userId;

  // sets current user id in all appropriate server contexts and
  // reruns subscriptions
  this._setUserId = options.setUserId || function () {};

  // Scratch data scoped to this connection (livedata_connection on the
  // client, livedata_session on the server). This is only used
  // internally, but we should have real and documented API for this
  // sort of thing someday.
  this._sessionData = options.sessionData;
};

_.extend(Meteor._MethodInvocation.prototype, {
  unblock: function () {
    var self = this;
    self._calledUnblock = true;
    self._unblock();
  },
  setUserId: function(userId) {
    var self = this;
    if (self._calledUnblock)
      throw new Error("Can't call setUserId in a method after calling unblock");
    self.userId = userId;
    self._setUserId(userId);
  }
});

Meteor._parseDDP = function (stringMessage) {
  try {
    var msg = JSON.parse(stringMessage);
  } catch (e) {
    Meteor._debug("Discarding message with invalid JSON", stringMessage);
    return null;
  }
  // DDP messages must be objects.
  if (msg === null || typeof msg !== 'object') {
    Meteor._debug("Discarding non-object DDP message", stringMessage);
    return null;
  }

  // massage msg to get it into "abstract ddp" rather than "wire ddp" format.

  // switch between "cleared" rep of unsetting fields and "undefined"
  // rep of same
  if (_.has(msg, 'cleared')) {
    if (!_.has(msg, 'fields'))
      msg.fields = {};
    _.each(msg.cleared, function (clearKey) {
      msg.fields[clearKey] = undefined;
    });
    delete msg.cleared;
  }

  _.each(['fields', 'params', 'result'], function (field) {
    if (_.has(msg, field))
      msg[field] = EJSON._adjustTypesFromJSONValue(msg[field]);
  });

  return msg;
};

Meteor._stringifyDDP = function (msg) {
  var copy = EJSON.clone(msg);
  // swizzle 'changed' messages from 'fields undefined' rep to 'fields
  // and cleared' rep
  if (_.has(msg, 'fields')) {
    var cleared = [];
    _.each(msg.fields, function (value, key) {
      if (value === undefined) {
        cleared.push(key);
        delete copy.fields[key];
      }
    });
    if (!_.isEmpty(cleared))
      copy.cleared = cleared;
    if (_.isEmpty(copy.fields))
      delete copy.fields;
  }
  // adjust types to basic
  _.each(['fields', 'params', 'result'], function (field) {
    if (_.has(copy, field))
      copy[field] = EJSON._adjustTypesToJSONValue(copy[field]);
  });
  if (msg.id && typeof msg.id !== 'string') {
    throw new Error("Message id is not a string");
  }
  return JSON.stringify(copy);
};

Meteor._CurrentInvocation = new Meteor.EnvironmentVariable;

// Note: The DDP server assumes that Meteor.Error EJSON-serializes as an object
// containing 'error' and optionally 'reason' and 'details'.
// The DDP client manually puts these into Meteor.Error objects. (We don't use
// EJSON.addType here because the type is determined by location in the
// protocol, not text on the wire.)
Meteor.Error = function (error, reason, details) {
  var self = this;

  // Currently, a numeric code, likely similar to a HTTP code (eg,
  // 404, 500). That is likely to change though.
  self.error = error;

  // Optional: A short human-readable summary of the error. Not
  // intended to be shown to end users, just developers. ("Not Found",
  // "Internal Server Error")
  self.reason = reason;

  // Optional: Additional information about the error, say for
  // debugging. It might be a (textual) stack trace if the server is
  // willing to provide one. The corresponding thing in HTTP would be
  // the body of a 404 or 500 response. (The difference is that we
  // never expect this to be shown to end users, only developers, so
  // it doesn't need to be pretty.)
  self.details = details;
};

Meteor.Error.prototype = new Error;
<commit_msg>Improve Meteor.Error<commit_after>Meteor._SUPPORTED_DDP_VERSIONS = [ 'pre1' ];

Meteor._MethodInvocation = function (options) {
  var self = this;

  // true if we're running not the actual method, but a stub (that is,
  // if we're on a client (which may be a browser, or in the future a
  // server connecting to another server) and presently running a
  // simulation of a server-side method for latency compensation
  // purposes). not currently true except in a client such as a browser,
  // since there's usually no point in running stubs unless you have a
  // zero-latency connection to the user.
  this.isSimulation = options.isSimulation;

  // XXX Backwards compatibility only. Remove this before 1.0.
  this.is_simulation = this.isSimulation;

  // call this function to allow other method invocations (from the
  // same client) to continue running without waiting for this one to
  // complete.
  this._unblock = options.unblock || function () {};
  this._calledUnblock = false;

  // current user id
  this.userId = options.userId;

  // sets current user id in all appropriate server contexts and
  // reruns subscriptions
  this._setUserId = options.setUserId || function () {};

  // Scratch data scoped to this connection (livedata_connection on the
  // client, livedata_session on the server). This is only used
  // internally, but we should have real and documented API for this
  // sort of thing someday.
  this._sessionData = options.sessionData;
};

_.extend(Meteor._MethodInvocation.prototype, {
  unblock: function () {
    var self = this;
    self._calledUnblock = true;
    self._unblock();
  },
  setUserId: function(userId) {
    var self = this;
    if (self._calledUnblock)
      throw new Error("Can't call setUserId in a method after calling unblock");
    self.userId = userId;
    self._setUserId(userId);
  }
});

Meteor._parseDDP = function (stringMessage) {
  try {
    var msg = JSON.parse(stringMessage);
  } catch (e) {
    Meteor._debug("Discarding message with invalid JSON", stringMessage);
    return null;
  }
  // DDP messages must be objects.
  if (msg === null || typeof msg !== 'object') {
    Meteor._debug("Discarding non-object DDP message", stringMessage);
    return null;
  }

  // massage msg to get it into "abstract ddp" rather than "wire ddp" format.

  // switch between "cleared" rep of unsetting fields and "undefined"
  // rep of same
  if (_.has(msg, 'cleared')) {
    if (!_.has(msg, 'fields'))
      msg.fields = {};
    _.each(msg.cleared, function (clearKey) {
      msg.fields[clearKey] = undefined;
    });
    delete msg.cleared;
  }

  _.each(['fields', 'params', 'result'], function (field) {
    if (_.has(msg, field))
      msg[field] = EJSON._adjustTypesFromJSONValue(msg[field]);
  });

  return msg;
};

Meteor._stringifyDDP = function (msg) {
  var copy = EJSON.clone(msg);
  // swizzle 'changed' messages from 'fields undefined' rep to 'fields
  // and cleared' rep
  if (_.has(msg, 'fields')) {
    var cleared = [];
    _.each(msg.fields, function (value, key) {
      if (value === undefined) {
        cleared.push(key);
        delete copy.fields[key];
      }
    });
    if (!_.isEmpty(cleared))
      copy.cleared = cleared;
    if (_.isEmpty(copy.fields))
      delete copy.fields;
  }
  // adjust types to basic
  _.each(['fields', 'params', 'result'], function (field) {
    if (_.has(copy, field))
      copy[field] = EJSON._adjustTypesToJSONValue(copy[field]);
  });
  if (msg.id && typeof msg.id !== 'string') {
    throw new Error("Message id is not a string");
  }
  return JSON.stringify(copy);
};

Meteor._CurrentInvocation = new Meteor.EnvironmentVariable;

// Note: The DDP server assumes that Meteor.Error EJSON-serializes as an object
// containing 'error' and optionally 'reason' and 'details'.
// The DDP client manually puts these into Meteor.Error objects. (We don't use
// EJSON.addType here because the type is determined by location in the
// protocol, not text on the wire.)
Meteor.Error = function (error, reason, details) {
  var self = this;

  // Meteor.Error is defined on both client and server, but it only
  // has a stack trace on the server. Luckily, Error.captureStackTrace
  // achieves this on V8. Googling and experimentation found no way to
  // capture stack traces on all browsers, so it seems that this is
  // not a viable general pattern for creating custom Error
  // classes. If we need to do that, we'd probably be best off setting
  // a 'type' property on the Error object instead.
  if (Error.captureStackTrace)
    Error.captureStackTrace(self, Meteor.Error);

  // Currently, a numeric code, likely similar to a HTTP code (eg,
  // 404, 500). That is likely to change though.
  self.error = error;

  // Optional: A short human-readable summary of the error. Not
  // intended to be shown to end users, just developers. ("Not Found",
  // "Internal Server Error")
  self.reason = reason;

  // Optional: Additional information about the error, say for
  // debugging. It might be a (textual) stack trace if the server is
  // willing to provide one. The corresponding thing in HTTP would be
  // the body of a 404 or 500 response. (The difference is that we
  // never expect this to be shown to end users, only developers, so
  // it doesn't need to be pretty.)
  self.details = details;

  // This is what gets displayed at the top of a stack trace. Current
  // format is "[404]" (if no reason is set) or "File not found [404]"
  if (self.reason)
    self.message = self.reason + ' [' + self.error + ']';
  else
    self.message = '[' + self.error + ']';
};

// http://davidshariff.com/blog/javascript-inheritance-patterns/
var inherits = function (child, parent) {
  var tmp = function () {};
  tmp.prototype = parent.prototype;
  child.prototype = new tmp;
  child.prototype.constructor = child;
};

inherits(Meteor.Error, Error);<|endoftext|>


<commit_before>var utils = require('../utils');

var Banking = function(foundWord, query, callback, cachedData){
	this._callback = callback;
	this.checkForTriggerWords(query, cachedData);
};

Banking.prototype.checkForTriggerWords = function(query, cachedData){
	var self = this,
		callback = this._callback;

	//check method
	this._checkMethod(query, cachedData);

	//trigger callback
	callback.trigger();
};

Banking.prototype._checkMethod = function(query, cachedData){
	var self = this;

	if(cachedData){
		switch(cachedData.method){
			case 'prepare_transfer':
				this._handleMakeTransfer(query, cachedData);
				break;
		}
		return;
	}

	utils.triggerWordsSwitch({
		query: query,
		triggerWords: [{
			words: ['current balance'],
			handler: function(){
				self._callback.method = 'check_balance';
			}
		},{
			words: ['banks in', 'rob a bank in'],
			handler: function(){
				self._callback.method = 'show_banks_in';
			}
		},{
			words: ['(latest|recent) transactions'],
			customRegex: true,
			handler: function(){
				self._callback.method = 'recent_transactions';
			}
		},{
			words: ['^prepare a transfer$'],
			customRegex: true,
			handler: function(){
				self._handleMakeTransfer(query, cachedData);
			}
		},{
			words: ['^show prepared transfers$'],
			customRegex: true,
			handler: function(){
				self._callback.method = 'show_prepared_transfers';
			}
		}],
		defaultAction: function(){
			self._callback.method = 'unknown';
		}
	});
};

Banking.prototype._handleMakeTransfer = function(query, cachedData){
	var details = cachedData && cachedData.details ? cachedData.details : {};

	this._callback.method = 'prepare_transfer';

	if(!details.amount){
		var match = query.match(/([0-9]+) (euro|dollar)/);
		if(match && match[1]){
			details.amount = match[1];
		}
	}

	if(!details.contactName){
		var match = query.match(/to ([^\s]+)/);
		if(match && match[1]){
			details.contactName = match[1];
		}
	}

	if(!details.description){
		var match = query.match(/with description (.+)/);
		if(match && match[1]){
			details.description = match[1];
		}
	}

	this._callback.details = details;
};

module.exports = Banking;<commit_msg>Fixes minor bug<commit_after>var utils = require('../utils');

var Banking = function(foundWord, query, callback, cachedData){
	this._callback = callback;
	this.checkForTriggerWords(query, cachedData);
};

Banking.prototype.checkForTriggerWords = function(query, cachedData){
	var self = this,
		callback = this._callback;

	//check method
	this._checkMethod(query, cachedData);

	//trigger callback
	callback.trigger();
};

Banking.prototype._checkMethod = function(query, cachedData){
	var self = this;

	if(cachedData){
		switch(cachedData.method){
			case 'prepare_transfer':
				this._handleMakeTransfer(query, cachedData);
				break;
		}
		return;
	}

	utils.triggerWordsSwitch({
		query: query,
		triggerWords: [{
			words: ['current balance'],
			handler: function(){
				self._callback.method = 'check_balance';
			}
		},{
			words: ['banks in', 'rob a bank in'],
			handler: function(){
				self._callback.method = 'show_banks_in';
			}
		},{
			words: ['(latest|recent) transactions'],
			customRegex: true,
			handler: function(){
				self._callback.method = 'recent_transactions';
			}
		},{
			words: ['^prepare a transfer'],
			customRegex: true,
			handler: function(){
				self._handleMakeTransfer(query, cachedData);
			}
		},{
			words: ['^show prepared transfers$'],
			customRegex: true,
			handler: function(){
				self._callback.method = 'show_prepared_transfers';
			}
		}],
		defaultAction: function(){
			self._callback.method = 'unknown';
		}
	});
};

Banking.prototype._handleMakeTransfer = function(query, cachedData){
	var details = cachedData && cachedData.details ? cachedData.details : {};

	this._callback.method = 'prepare_transfer';

	if(!details.amount){
		var match = query.match(/([0-9]+) (euro|dollar)/);
		if(match && match[1]){
			details.amount = match[1];
		}
	}

	if(!details.contactName){
		var match = query.match(/to ([^\s]+)/);
		if(match && match[1]){
			details.contactName = match[1];
		}
	}

	if(!details.description){
		var match = query.match(/with description (.+)/);
		if(match && match[1]){
			details.description = match[1];
		}
	}

	this._callback.details = details;
};

module.exports = Banking;<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { addFlashMessage } from '../actions/flashMessageAction';

class EnsureLoggedIn extends React.Component {
  componentDidMount() {
    const { isLoggedIn } = this.props;

    if (!isLoggedIn) {
      this.props.addFlashMessage({
        type: 'error',
        text: 'You need to signvin to access this page'
      });
      this.props.history.push('/signin');
    }
  }

  render() {
    const { isLoggedIn } = this.props;
    if (isLoggedIn) {
      return this.props.children;
    }
    return null;
  }
}

EnsureLoggedIn.propTypes = {
  isLoggedIn: PropTypes.bool.isRequired,
  addFlashMessage: PropTypes.func.isRequired
};

function mapStateToProps(state) {
  return {
    isLoggedIn: state.signedInUser.isAuthenticated
  };
}

export default withRouter(connect(mapStateToProps, { addFlashMessage })(EnsureLoggedIn));

<commit_msg>Corrects a typo in a flash message<commit_after>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { addFlashMessage } from '../actions/flashMessageAction';

class EnsureLoggedIn extends React.Component {
  componentDidMount() {
    const { isLoggedIn } = this.props;

    if (!isLoggedIn) {
      this.props.addFlashMessage({
        type: 'error',
        text: 'You need to sign in to access this page'
      });
      this.props.history.push('/signin');
    }
  }

  render() {
    const { isLoggedIn } = this.props;
    if (isLoggedIn) {
      return this.props.children;
    }
    return null;
  }
}

EnsureLoggedIn.propTypes = {
  isLoggedIn: PropTypes.bool.isRequired,
  addFlashMessage: PropTypes.func.isRequired
};

function mapStateToProps(state) {
  return {
    isLoggedIn: state.signedInUser.isAuthenticated
  };
}

export default withRouter(connect(mapStateToProps, { addFlashMessage })(EnsureLoggedIn));

<|endoftext|>


<commit_before>import Notifications from '../lib/collections/notifications/collection.js';
import Messages from '../lib/collections/messages/collection.js';
import Conversations from '../lib/collections/conversations/collection.js';

import Localgroups from '../lib/collections/localgroups/collection.js';
import Users from 'meteor/vulcan:users';
import { Posts } from '../lib/collections/posts';
import { Comments } from '../lib/collections/comments'
import { renderAndSendEmail, reasonUserCantReceiveEmails } from './emails/renderEmail.js';
import './emailComponents/EmailWrapper.jsx';
import './emailComponents/NewPostEmail.jsx';
import './emailComponents/PrivateMessagesEmail.jsx';
import { EventDebouncer } from './debouncer.js';

import { addCallback, newMutation } from 'meteor/vulcan:core';

import { Components } from 'meteor/vulcan:core';
import React from 'react';
import keyBy from 'lodash/keyBy';

const createNotifications = (userIds, notificationType, documentType, documentId) => {
  userIds.forEach(userId => {

    let user = Users.findOne({ _id:userId });

    let notificationData = {
      userId: userId,
      documentId: documentId,
      documentType: documentType,
      message: notificationMessage(notificationType, documentType, documentId),
      type: notificationType,
      link: getLink(documentType, documentId),
    }

    newMutation({
      action: 'notifications.new',
      collection: Notifications,
      document: notificationData,
      currentUser: user,
      validate: false
    });
  });
}

const sendPostByEmail = async (users, postId, reason) => {
  let post = Posts.findOne(postId);

  for(let user of users) {
    if(!reasonUserCantReceiveEmails(user)) {
      await renderAndSendEmail({
        user,
        subject: post.title,
        bodyComponent: <Components.EmailWrapper>
          <Components.NewPostEmail documentId={post._id} reason={reason}/>
        </Components.EmailWrapper>
      });
    } else {
      //eslint-disable-next-line no-console
      console.log(`Skipping user ${user.username} when emailing: ${reasonUserCantReceiveEmails(user)}`);
    }
  }
}

const getLink = (documentType, documentId) => {
  let document = getDocument(documentType, documentId);

  switch(documentType) {
    case "post":
      return Posts.getPageUrl(document);
    case "comment":
      return Comments.getPageUrl(document);
    case "user":
      return Users.getProfileUrl(document);
    case "message":
      return Messages.getLink(document);
    default:
      //eslint-disable-next-line no-console
      console.error("Invalid notification type");
  }
}

const notificationMessage = (notificationType, documentType, documentId) => {
  let document = getDocument(documentType, documentId);
  let group = {}
  if (documentType == "post" && document.groupId) {
    group = Localgroups.findOne(document.groupId);
  }

  switch(notificationType) {
    case "newPost":
      return Posts.getAuthorName(document) + ' has created a new post: ' + document.title;
    case "newPendingPost":
      return Posts.getAuthorName(document) + ' has a new post pending approval ' + document.title;
    case "postApproved":
      return 'Your post "' + document.title + '" has been approved';
    case "newEvent":
        return Posts.getAuthorName(document) + ' has created a new event in the group "' + group.name + '"';
    case "newGroupPost":
        return Posts.getAuthorName(document) + ' has created a new post in the group "' + group.name + '"';
    case "newComment":
      return Comments.getAuthorName(document) + ' left a new comment on "' + Posts.findOne(document.postId).title + '"';
    case "newReply":
      return Comments.getAuthorName(document) + ' replied to a comment on "' + Posts.findOne(document.postId).title + '"';
    case "newReplyToYou":
        return Comments.getAuthorName(document) + ' replied to your comment on "' + Posts.findOne(document.postId).title + '"';
    case "newUser":
      return document.displayName + ' just signed up!';
    case "newMessage":
      let conversation = Conversations.findOne(document.conversationId);
      return Users.findOne(document.userId).displayName + ' sent you a new message' + (conversation.title ? (' in the conversation ' + conversation.title) : "") + '!';
    default:
      //eslint-disable-next-line no-console
      console.error("Invalid notification type");
  }
}

const getDocument = (documentType, documentId) => {
  switch(documentType) {
    case "post":
      return Posts.findOne(documentId);
    case "comment":
      return Comments.findOne(documentId);
    case "user":
      return Users.findOne(documentId);
    case "message":
      return Messages.findOne(documentId);
    default:
      //eslint-disable-next-line no-console
      console.error("Invalid documentType type");
  }
}

/**
 * @summary Add notification callback when a post is approved
 */
function PostsApprovedNotification(post) {
  createNotifications([post.userId], 'postApproved', 'post', post._id);
}
addCallback("posts.approve.async", PostsApprovedNotification);

function PostsUndraftNotification(post) {
  //eslint-disable-next-line no-console
  console.info("Post undrafted, creating notifications");

  postsNewNotifications(post);
}
addCallback("posts.undraft.async", PostsUndraftNotification);

// Add new post notification callback on post submit
function postsNewNotifications (post) {
  if (!post.draft && post.status === Posts.config.STATUS_APPROVED) {
    // Removed because this was useless. Will be reintroduced in a different
    // form (with advanced filters and emailing.)
    //let usersToNotify = _.pluck(Users.find({'notifications_posts': true}, {fields: {_id:1}}).fetch(), '_id');
    let usersToNotify = [];

    // add users who are subscribed to this post's author
    const postAuthor = Users.findOne(post.userId);
    if (!!postAuthor.subscribers) {
      usersToNotify = _.union(usersToNotify, postAuthor.subscribers);
    }

    // add users who are subscribed to this post's groups
    if (post.groupId) {
      const group = Localgroups.findOne(post.groupId);
      if (group.subscribers) {
        usersToNotify = _.union(usersToNotify, group.subscribers);
      }
    }
    // remove this post's author
    usersToNotify = _.without(usersToNotify, post.userId);

    if (post.groupId && post.isEvent) {
      createNotifications(usersToNotify, 'newEvent', 'post', post._id);
    } else if (post.groupId && !post.isEvent) {
      createNotifications(usersToNotify, 'newGroupPost', 'post', post._id);
    } else {
      createNotifications(usersToNotify, 'newPost', 'post', post._id);
    }

  }
}
addCallback("posts.new.async", postsNewNotifications);

function findUsersToEmail(filter) {
  let usersMatchingFilter = Users.find(filter, {fields: {_id:1, email:1, emails:1}}).fetch();

  let usersToEmail = usersMatchingFilter.filter(u => {
    if (u.email && u.emails && u.emails.length) {
      let primaryAddress = u.email;

      for(let i=0; i<u.emails.length; i++)
      {
        if(u.emails[i].address === primaryAddress && u.emails[i].verified)
          return true;
      }
      return false;
    } else {
      return false;
    }
  });
  return usersToEmail
}

function PostsCurateNotification (post, oldPost) {
  if(post.curatedDate && !oldPost.curatedDate) {
    let usersToEmail = findUsersToEmail({'emailSubscribedToCurated': true});
    sendPostByEmail(usersToEmail, post._id, "you have the \"Email me new posts in Curated\" option enabled");
  }
}
addCallback("posts.edit.async", PostsCurateNotification);

// add new comment notification callback on comment submit
async function CommentsNewNotifications(comment) {
  // note: dummy content has disableNotifications set to true
  if(Meteor.isServer && !comment.disableNotifications) {

    const post = await Posts.findOne(comment.postId);

    // keep track of whom we've notified (so that we don't notify the same user twice for one comment,
    // if e.g. they're both the author of the post and the author of a comment being replied to)
    let notifiedUsers = [];

    // 1. Notify users who are subscribed to the parent comment
    if (!!comment.parentCommentId) {
      const parentComment = Comments.findOne(comment.parentCommentId);

      if (!!parentComment.subscribers && !!parentComment.subscribers.length) {
        // remove userIds of users that have already been notified
        // and of comment and parentComment author (they could be replying in a thread they're subscribed to)
        let parentCommentSubscribersToNotify = _.difference(parentComment.subscribers, notifiedUsers, [comment.userId, parentComment.userId]);
        createNotifications(parentCommentSubscribersToNotify, 'newReply', 'comment', comment._id);
        notifiedUsers = [...notifiedUsers, ...parentCommentSubscribersToNotify];

        // Separately notify author of comment with different notification, if they are subscribed, and are NOT the author of the comment
        if (parentComment.subscribers.includes(parentComment.userId) && parentComment.userId !== comment.userId) {
          createNotifications([parentComment.userId], 'newReplyToYou', 'comment', comment._id);
          notifiedUsers = [...notifiedUsers, parentComment.userId];
        }
      }
    }

    // 2. Notify users who are subscribed to the post (which may or may not include the post's author)
    if (post && post.subscribers && post.subscribers.length) {
      // remove userIds of users that have already been notified
      // and of comment author (they could be replying in a thread they're subscribed to)
      let postSubscribersToNotify = _.difference(post.subscribers, notifiedUsers, [comment.userId]);
      createNotifications(postSubscribersToNotify, 'newComment', 'comment', comment._id);
    }
  }
}
addCallback("comments.new.async", CommentsNewNotifications);

async function sendPrivateMessagesEmail(conversationId, messageIds) {
  const conversation = await Conversations.findOne(conversationId);
  const participants = await Users.find({_id: {$in: conversation.participantIds}}).fetch();
  const participantsById = keyBy(participants, u=>u._id);
  const messages = await Messages.find(
    {_id: {$in: messageIds}},
    { sort: {createdAt:1} })
    .fetch();

  for (const recipientUser of participants)
  {
    // TODO: Gradual rollout--only email admins with this. Remove later when
    // this is more tested.
    if (!Users.isAdmin(recipientUser))
      continue;

    const otherParticipants = _.filter(participants, u=>u._id != recipientUser._id);
    const subject = `Private message conversation with ${otherParticipants.map(u=>u.displayName).join(', ')}`;

    await renderAndSendEmail({
      user: recipientUser,
      subject: subject,
      bodyComponent: <Components.EmailWrapper>
        <Components.PrivateMessagesEmail
          conversation={conversation}
          messages={messages}
          participantsById={participantsById}
        />
      </Components.EmailWrapper>
    });
  }
}

const privateMessagesDebouncer = new EventDebouncer({
  name: "privateMessage",
  delayMinutes: 15,
  maxDelayMinutes: 30,
  callback: sendPrivateMessagesEmail
});

function messageNewNotification(message) {
  const conversationId = message.conversationId;
  const conversation = Conversations.findOne(conversationId);

  // For on-site notifications, notify everyone except the sender of the
  // message. For email notifications, notify everyone including the sender
  // (since if there's a back-and-forth in the grouped notifications, you want
  // to see your own messages.)
  const recipients = conversation.participantIds.filter((id) => (id !== message.userId));

  // Create on-site notification
  createNotifications(recipients, 'newMessage', 'message', message._id);

  // Generate debounced email notifications
  privateMessagesDebouncer.recordEvent(conversationId, message._id);
}
addCallback("messages.new.async", messageNewNotification);
<commit_msg>Don't send people their own outgoing PMs except as context<commit_after>import Notifications from '../lib/collections/notifications/collection.js';
import Messages from '../lib/collections/messages/collection.js';
import Conversations from '../lib/collections/conversations/collection.js';

import Localgroups from '../lib/collections/localgroups/collection.js';
import Users from 'meteor/vulcan:users';
import { Posts } from '../lib/collections/posts';
import { Comments } from '../lib/collections/comments'
import { renderAndSendEmail, reasonUserCantReceiveEmails } from './emails/renderEmail.js';
import './emailComponents/EmailWrapper.jsx';
import './emailComponents/NewPostEmail.jsx';
import './emailComponents/PrivateMessagesEmail.jsx';
import { EventDebouncer } from './debouncer.js';

import { addCallback, newMutation } from 'meteor/vulcan:core';

import { Components } from 'meteor/vulcan:core';
import React from 'react';
import keyBy from 'lodash/keyBy';

const createNotifications = (userIds, notificationType, documentType, documentId) => {
  userIds.forEach(userId => {

    let user = Users.findOne({ _id:userId });

    let notificationData = {
      userId: userId,
      documentId: documentId,
      documentType: documentType,
      message: notificationMessage(notificationType, documentType, documentId),
      type: notificationType,
      link: getLink(documentType, documentId),
    }

    newMutation({
      action: 'notifications.new',
      collection: Notifications,
      document: notificationData,
      currentUser: user,
      validate: false
    });
  });
}

const sendPostByEmail = async (users, postId, reason) => {
  let post = Posts.findOne(postId);

  for(let user of users) {
    if(!reasonUserCantReceiveEmails(user)) {
      await renderAndSendEmail({
        user,
        subject: post.title,
        bodyComponent: <Components.EmailWrapper>
          <Components.NewPostEmail documentId={post._id} reason={reason}/>
        </Components.EmailWrapper>
      });
    } else {
      //eslint-disable-next-line no-console
      console.log(`Skipping user ${user.username} when emailing: ${reasonUserCantReceiveEmails(user)}`);
    }
  }
}

const getLink = (documentType, documentId) => {
  let document = getDocument(documentType, documentId);

  switch(documentType) {
    case "post":
      return Posts.getPageUrl(document);
    case "comment":
      return Comments.getPageUrl(document);
    case "user":
      return Users.getProfileUrl(document);
    case "message":
      return Messages.getLink(document);
    default:
      //eslint-disable-next-line no-console
      console.error("Invalid notification type");
  }
}

const notificationMessage = (notificationType, documentType, documentId) => {
  let document = getDocument(documentType, documentId);
  let group = {}
  if (documentType == "post" && document.groupId) {
    group = Localgroups.findOne(document.groupId);
  }

  switch(notificationType) {
    case "newPost":
      return Posts.getAuthorName(document) + ' has created a new post: ' + document.title;
    case "newPendingPost":
      return Posts.getAuthorName(document) + ' has a new post pending approval ' + document.title;
    case "postApproved":
      return 'Your post "' + document.title + '" has been approved';
    case "newEvent":
        return Posts.getAuthorName(document) + ' has created a new event in the group "' + group.name + '"';
    case "newGroupPost":
        return Posts.getAuthorName(document) + ' has created a new post in the group "' + group.name + '"';
    case "newComment":
      return Comments.getAuthorName(document) + ' left a new comment on "' + Posts.findOne(document.postId).title + '"';
    case "newReply":
      return Comments.getAuthorName(document) + ' replied to a comment on "' + Posts.findOne(document.postId).title + '"';
    case "newReplyToYou":
        return Comments.getAuthorName(document) + ' replied to your comment on "' + Posts.findOne(document.postId).title + '"';
    case "newUser":
      return document.displayName + ' just signed up!';
    case "newMessage":
      let conversation = Conversations.findOne(document.conversationId);
      return Users.findOne(document.userId).displayName + ' sent you a new message' + (conversation.title ? (' in the conversation ' + conversation.title) : "") + '!';
    default:
      //eslint-disable-next-line no-console
      console.error("Invalid notification type");
  }
}

const getDocument = (documentType, documentId) => {
  switch(documentType) {
    case "post":
      return Posts.findOne(documentId);
    case "comment":
      return Comments.findOne(documentId);
    case "user":
      return Users.findOne(documentId);
    case "message":
      return Messages.findOne(documentId);
    default:
      //eslint-disable-next-line no-console
      console.error("Invalid documentType type");
  }
}

/**
 * @summary Add notification callback when a post is approved
 */
function PostsApprovedNotification(post) {
  createNotifications([post.userId], 'postApproved', 'post', post._id);
}
addCallback("posts.approve.async", PostsApprovedNotification);

function PostsUndraftNotification(post) {
  //eslint-disable-next-line no-console
  console.info("Post undrafted, creating notifications");

  postsNewNotifications(post);
}
addCallback("posts.undraft.async", PostsUndraftNotification);

// Add new post notification callback on post submit
function postsNewNotifications (post) {
  if (!post.draft && post.status === Posts.config.STATUS_APPROVED) {
    // Removed because this was useless. Will be reintroduced in a different
    // form (with advanced filters and emailing.)
    //let usersToNotify = _.pluck(Users.find({'notifications_posts': true}, {fields: {_id:1}}).fetch(), '_id');
    let usersToNotify = [];

    // add users who are subscribed to this post's author
    const postAuthor = Users.findOne(post.userId);
    if (!!postAuthor.subscribers) {
      usersToNotify = _.union(usersToNotify, postAuthor.subscribers);
    }

    // add users who are subscribed to this post's groups
    if (post.groupId) {
      const group = Localgroups.findOne(post.groupId);
      if (group.subscribers) {
        usersToNotify = _.union(usersToNotify, group.subscribers);
      }
    }
    // remove this post's author
    usersToNotify = _.without(usersToNotify, post.userId);

    if (post.groupId && post.isEvent) {
      createNotifications(usersToNotify, 'newEvent', 'post', post._id);
    } else if (post.groupId && !post.isEvent) {
      createNotifications(usersToNotify, 'newGroupPost', 'post', post._id);
    } else {
      createNotifications(usersToNotify, 'newPost', 'post', post._id);
    }

  }
}
addCallback("posts.new.async", postsNewNotifications);

function findUsersToEmail(filter) {
  let usersMatchingFilter = Users.find(filter, {fields: {_id:1, email:1, emails:1}}).fetch();

  let usersToEmail = usersMatchingFilter.filter(u => {
    if (u.email && u.emails && u.emails.length) {
      let primaryAddress = u.email;

      for(let i=0; i<u.emails.length; i++)
      {
        if(u.emails[i].address === primaryAddress && u.emails[i].verified)
          return true;
      }
      return false;
    } else {
      return false;
    }
  });
  return usersToEmail
}

function PostsCurateNotification (post, oldPost) {
  if(post.curatedDate && !oldPost.curatedDate) {
    let usersToEmail = findUsersToEmail({'emailSubscribedToCurated': true});
    sendPostByEmail(usersToEmail, post._id, "you have the \"Email me new posts in Curated\" option enabled");
  }
}
addCallback("posts.edit.async", PostsCurateNotification);

// add new comment notification callback on comment submit
async function CommentsNewNotifications(comment) {
  // note: dummy content has disableNotifications set to true
  if(Meteor.isServer && !comment.disableNotifications) {

    const post = await Posts.findOne(comment.postId);

    // keep track of whom we've notified (so that we don't notify the same user twice for one comment,
    // if e.g. they're both the author of the post and the author of a comment being replied to)
    let notifiedUsers = [];

    // 1. Notify users who are subscribed to the parent comment
    if (!!comment.parentCommentId) {
      const parentComment = Comments.findOne(comment.parentCommentId);

      if (!!parentComment.subscribers && !!parentComment.subscribers.length) {
        // remove userIds of users that have already been notified
        // and of comment and parentComment author (they could be replying in a thread they're subscribed to)
        let parentCommentSubscribersToNotify = _.difference(parentComment.subscribers, notifiedUsers, [comment.userId, parentComment.userId]);
        createNotifications(parentCommentSubscribersToNotify, 'newReply', 'comment', comment._id);
        notifiedUsers = [...notifiedUsers, ...parentCommentSubscribersToNotify];

        // Separately notify author of comment with different notification, if they are subscribed, and are NOT the author of the comment
        if (parentComment.subscribers.includes(parentComment.userId) && parentComment.userId !== comment.userId) {
          createNotifications([parentComment.userId], 'newReplyToYou', 'comment', comment._id);
          notifiedUsers = [...notifiedUsers, parentComment.userId];
        }
      }
    }

    // 2. Notify users who are subscribed to the post (which may or may not include the post's author)
    if (post && post.subscribers && post.subscribers.length) {
      // remove userIds of users that have already been notified
      // and of comment author (they could be replying in a thread they're subscribed to)
      let postSubscribersToNotify = _.difference(post.subscribers, notifiedUsers, [comment.userId]);
      createNotifications(postSubscribersToNotify, 'newComment', 'comment', comment._id);
    }
  }
}
addCallback("comments.new.async", CommentsNewNotifications);

async function sendPrivateMessagesEmail(conversationId, messageIds) {
  const conversation = await Conversations.findOne(conversationId);
  const participants = await Users.find({_id: {$in: conversation.participantIds}}).fetch();
  const participantsById = keyBy(participants, u=>u._id);
  const messages = await Messages.find(
    {_id: {$in: messageIds}},
    { sort: {createdAt:1} })
    .fetch();

  for (const recipientUser of participants)
  {
    // TODO: Gradual rollout--only email admins with this. Remove later when
    // this is more tested.
    if (!Users.isAdmin(recipientUser))
      continue;

    // If this user is responsible for every message that would be in the
    // email, don't send it to them (you only want emails that contain at
    // least one message that's not your own; your own messages are optional
    // context).
    if (!_.some(messages, message=>message.userId !== recipientUser._id))
      continue;

    const otherParticipants = _.filter(participants, u=>u._id != recipientUser._id);
    const subject = `Private message conversation with ${otherParticipants.map(u=>u.displayName).join(', ')}`;

    await renderAndSendEmail({
      user: recipientUser,
      subject: subject,
      bodyComponent: <Components.EmailWrapper>
        <Components.PrivateMessagesEmail
          conversation={conversation}
          messages={messages}
          participantsById={participantsById}
        />
      </Components.EmailWrapper>
    });
  }
}

const privateMessagesDebouncer = new EventDebouncer({
  name: "privateMessage",
  delayMinutes: 15,
  maxDelayMinutes: 30,
  callback: sendPrivateMessagesEmail
});

function messageNewNotification(message) {
  const conversationId = message.conversationId;
  const conversation = Conversations.findOne(conversationId);

  // For on-site notifications, notify everyone except the sender of the
  // message. For email notifications, notify everyone including the sender
  // (since if there's a back-and-forth in the grouped notifications, you want
  // to see your own messages.)
  const recipients = conversation.participantIds.filter((id) => (id !== message.userId));

  // Create on-site notification
  createNotifications(recipients, 'newMessage', 'message', message._id);

  // Generate debounced email notifications
  privateMessagesDebouncer.recordEvent(conversationId, message._id);
}
addCallback("messages.new.async", messageNewNotification);
<|endoftext|>


<commit_before>angular.module('asics').controller('InvitationCtrl', [
  '$mdToast',
  '$q',
  '$scope',
  '$state',
  '$stateParams',
  'admin',
  'countries',
  function ($mdToast, $q, $scope, $state, $stateParams, admin, countries) {
    $scope.strings = {};
    $scope.csvInvitation = false;
    $scope.occupations = [];
    $scope.countries = countries;
    $scope.language = $stateParams.language;
    $scope.guest = {};
    $scope.isAthlete = false;
    $scope.athleteGuest = {};
    $scope.inputFile = '';

    // Get all strings
    angular.copy(adminInvitationStrings[$scope.language], $scope.strings);
    // Get all guest occupations
    angular.copy(occupationStrings[$scope.language], $scope.occupations);

    $scope.athletes = [
      'Jordan Ernest BURROUGHS',
      'Saori YOSHIDA',
      'Yoshihide Kiryu',
      'Chirstophe Lemaitre',
      'Floria Guei',
      'Yohan Diniz',
      'Queen Harrison',
      'Candace Hill',
      'Lolo Jones',
      'Veronica Shanti Pereira',
      'Jieshi Neo',
      'Jared Tallent',
      'Bruno Rezende',
      'Gael Monfils',
      'CoCo Vandeweghe',
      'Steve Johnson',
      'Vasek Pospisil',
      'Gwen Jorgensen',
      'Kerri Walsh',
      'Ramu Tokashiki',
      'Alexandra Priscila do Nascimento'
    ];

    $scope.inviteGuest = function () {
      var postGuest = {};

      if ($scope.isAthlete) {
        postGuest.occupation = 'Atleta Asics';
        postGuest.name = $scope.athleteGuest.name;
      } else {
        postGuest.occupation = $scope.guest.occupation;
        postGuest.name = $scope.guest.name;
      }

      postGuest.language = $scope.guest.language;
      postGuest.email = $scope.guest.email;
      postGuest.country = countries.PT[$scope.guest.country_key];

      admin.postInvite(postGuest)
        .then(onInviteSuccess)
        .catch(errorToast)
    };

    function onInviteSuccess(guest) {
      successToast(guest);
      clearForm();
    }

    function errorToast(error) {
      var errorString;

      if (error.error == "ValidationFailed")
        errorString = "Error validating " + Object.keys(error.message)[0];
      else
        errorString = error.message;

      $mdToast.show(
        $mdToast.simple()
          .textContent(errorString)
          .position('top right')
          .hideDelay(4000)
          .theme('error-toast')
      );
    }

    function successToast(guest) {
      $mdToast.show(
        $mdToast.simple()
          .textContent("Invitation sent to " + guest.name)
          .position('top right')
          .hideDelay(4000)
          .theme('success-toast')
      );
      clearForm();
    }

    function clearForm() {
      $scope.guest = {
        email: '',
        name: '',
        occupation: '',
        language: 'PT',
        country_key: 'BR'
      };

      $scope.athleteGuest = {
        name: ''
      };

      $scope.isAthlete = false;

      $scope.adminForm.$setPristine();
      $scope.adminForm.email.$setUntouched();

      if ($scope.adminForm.name)
        $scope.adminForm.name.$setUntouched();
      if ($scope.adminForm.occupation)
        $scope.adminForm.occupation.$setUntouched();
    }

    $scope.$on('$viewContentLoaded', function () {
      clearForm();
    });
  }
]);

var adminInvitationStrings = {
  EN: {
    newInvitation: "New invitation",
    title: "Hello! Fill the fields below to invite someone for the Asics Hub.",
    csvTitle: "You can import a .csv file with your guest list.",
    asicsAthlete: "Asics Athlete",
    formName: "Name",
    formNameRequireError: "Please, fill the name",
    chooseAthlete: "Choose the athlete",
    formOccupationRequireError: "Please, choose the occupation of the guest",
    formCountryRequireError: "Please, choose the country of the guest",
    formEmail: "Email",
    formEmailRequiredError: "Please, fill the email",
    formEmailInvalidError: "Invalid email",
    formLanguage: "Language",
    formOccupation: "Occupation",
    formOccupationOption: "Choose the occupation of the guest",
    formCountry: "Country",
    formCountryOption: "Guest country",
    formCsvEmailInfo: "Once your file is process, an email will be sent for you to notify you.",
    formFile: "File",
    formFileRequiredError: "Please, insert a .csv file",
    formFileSelect: "Choose a .csv file",
    formFileSelected: "File selected",
    sendInvitation: "SEND INVITATION",
    sendCsvFile: "SEND FILE"
  },
  PT: {
    newInvitation: "Novo convite",
    title: "Olá, preencha os campos abaixo para convidar uma pessoa para o Asics Hub.",
    csvTitle: "Você pode importar um arquivo .csv com a lista de convidados.",
    asicsAthlete: "Atleta Asics",
    formName: "Nome",
    formNameRequireError: "Por favor, preencha o nome",
    chooseAthlete: "Escolha o atleta",
    formOccupationRequireError: "Por favor, escolha a área de atuação do convidado",
    formCountryRequireError: "Por favor, escolha o país do convidado",
    formEmail: "Email",
    formEmailRequiredError: "Por favor, preencha o email",
    formEmailInvalidError: "Email inválido",
    formLanguage: "Idioma",
    formOccupation: "Função",
    formOccupationOption: "Escolha a função do convidado",
    formCountry: "País",
    formCountryOption: "País do convidado",
    formCsvEmailInfo: "Uma vez que seu arquivo for processado, um email será disparado para você notificando.",
    formFile: "Arquivo",
    formFileRequiredError: "Por favor, insira o arquivo",
    formFileSelect: "Escolher arquivo .csv",
    formFileSelected: "Arquivo selecionado",
    sendInvitation: "ENVIAR CONVITE",
    sendCsvFile: "ENVIAR ARQUIVO"
  }
};

var occupationStrings = {
  PT: [
    'Atleta',
    'Staff de treinamento',
    'Federação / NOC oficial',
    'Mídia',
    'Família/Amigo de atleta',
    'Diretor de corrida',
    'Representante do atleta',
    'Outro'
  ],
  EN: [
    'Athlete',
    'Coaching staff',
    'Federation / NOC official',
    'Media',
    'Athlete Family / Friends',
    'Race Director',
    'Athlete Representatives',
    'Other'
  ]
};
<commit_msg>removed visibility hidden on print page<commit_after>angular.module('asics').controller('InvitationCtrl', [
  '$mdToast',
  '$q',
  '$scope',
  '$state',
  '$stateParams',
  'admin',
  'countries',
  function ($mdToast, $q, $scope, $state, $stateParams, admin, countries) {
    $scope.strings = {};
    $scope.csvInvitation = false;
    $scope.occupations = [];
    $scope.countries = countries;
    $scope.language = $stateParams.language;
    $scope.guest = {};
    $scope.isAthlete = false;
    $scope.athleteGuest = {};
    $scope.inputFile = '';

    // Get all strings
    angular.copy(adminInvitationStrings[$scope.language], $scope.strings);
    // Get all guest occupations
    angular.copy(occupationStrings[$scope.language], $scope.occupations);

    $scope.athletes = [
      'Jordan Ernest BURROUGHS',
      'Saori YOSHIDA',
      'Yoshihide Kiryu',
      'Chirstophe Lemaitre',
      'Floria Guei',
      'Yohan Diniz',
      'Queen Harrison',
      'Candace Hill',
      'Lolo Jones',
      'Veronica Shanti Pereira',
      'Jieshi Neo',
      'Jared Tallent',
      'Bruno Rezende',
      'Gael Monfils',
      'CoCo Vandeweghe',
      'Steve Johnson',
      'Vasek Pospisil',
      'Gwen Jorgensen',
      'Kerri Walsh',
      'Ramu Tokashiki',
      'Alexandra Priscila do Nascimento'
    ];

    $scope.inviteGuest = function () {
      var postGuest = {};

      if ($scope.isAthlete) {
        postGuest.occupation = 'Atleta Asics';
        postGuest.name = $scope.athleteGuest.name;
      } else {
        postGuest.occupation = $scope.guest.occupation;
        postGuest.name = $scope.guest.name;
      }

      postGuest.language = $scope.guest.language;
      postGuest.email = $scope.guest.email;
      postGuest.country = countries.PT[$scope.guest.country_key];

      admin.postInvite(postGuest)
        .then(onInviteSuccess)
        .catch(errorToast)
    };

    function onInviteSuccess(guest) {
      successToast(guest);
      clearForm();
    }

    function errorToast(error) {
      var errorString;

      if (error.error == "ValidationFailed")
        errorString = "Error validating " + Object.keys(error.message)[0];
      else
        errorString = error.message;

      $mdToast.show(
        $mdToast.simple()
          .textContent(errorString)
          .position('top right')
          .hideDelay(4000)
          .theme('error-toast')
      );
    }

    function successToast(guest) {
      $mdToast.show(
        $mdToast.simple()
          .textContent("Invitation sent to " + guest.name)
          .position('top right')
          .hideDelay(4000)
          .theme('success-toast')
      );
      clearForm();
    }

    function clearForm() {
      $scope.guest = {
        email: '',
        name: '',
        occupation: '',
        language: 'PT',
        country_key: 'BR'
      };

      $scope.athleteGuest = {
        name: ''
      };

      $scope.isAthlete = false;

      $scope.adminForm.$setPristine();
      $scope.adminForm.email.$setUntouched();

      if ($scope.adminForm.name)
        $scope.adminForm.name.$setUntouched();
      if ($scope.adminForm.occupation)
        $scope.adminForm.occupation.$setUntouched();
    }

    $scope.$on('$viewContentLoaded', function () {
      clearForm();
    });
  }
]);

var adminInvitationStrings = {
  EN: {
    newInvitation: "New invitation",
    title: "Hello! Fill the fields below to invite someone for the Asics Hub.",
    csvTitle: "You can import a .csv file with your guest list.",
    asicsAthlete: "Asics Athlete",
    formName: "Name",
    formNameRequireError: "Please, fill the name",
    chooseAthlete: "Choose the athlete",
    formOccupationRequireError: "Please, choose the occupation of the guest",
    formCountryRequireError: "Please, choose the country of the guest",
    formEmail: "Email",
    formEmailRequiredError: "Please, fill the email",
    formEmailInvalidError: "Invalid email",
    formLanguage: "Language",
    formOccupation: "Occupation",
    formOccupationOption: "Choose the occupation of the guest",
    formCountry: "Country",
    formCountryOption: "Guest country",
    formCsvEmailInfo: "Once your file is process, an email will be sent for you to notify you.",
    formFile: "File",
    formFileRequiredError: "Please, insert a .csv file",
    formFileSelect: "Choose a .csv file",
    formFileSelected: "File selected",
    sendInvitation: "SEND INVITATION",
    sendCsvFile: "SEND FILE"
  },
  PT: {
    newInvitation: "Novo convite",
    title: "Olá, preencha os campos abaixo para convidar uma pessoa para o Asics Hub.",
    csvTitle: "Você pode importar um arquivo .csv com a lista de convidados.",
    asicsAthlete: "Atleta Asics",
    formName: "Nome",
    formNameRequireError: "Por favor, preencha o nome",
    chooseAthlete: "Escolha o atleta",
    formOccupationRequireError: "Por favor, escolha a área de atuação do convidado",
    formCountryRequireError: "Por favor, escolha o país do convidado",
    formEmail: "Email",
    formEmailRequiredError: "Por favor, preencha o email",
    formEmailInvalidError: "Email inválido",
    formLanguage: "Idioma",
    formOccupation: "Função",
    formOccupationOption: "Escolha a função do convidado",
    formCountry: "País",
    formCountryOption: "País do convidado",
    formCsvEmailInfo: "Uma vez que seu arquivo for processado, um email será disparado para você notificando.",
    formFile: "Arquivo",
    formFileRequiredError: "Por favor, insira o arquivo",
    formFileSelect: "Selecionar arquivo .csv",
    formFileSelected: "Arquivo selecionado",
    sendInvitation: "ENVIAR CONVITE",
    sendCsvFile: "ENVIAR ARQUIVO"
  }
};

var occupationStrings = {
  PT: [
    'Atleta',
    'Staff de treinamento',
    'Federação / NOC oficial',
    'Mídia',
    'Família/Amigo de atleta',
    'Diretor de corrida',
    'Representante do atleta',
    'Outro'
  ],
  EN: [
    'Athlete',
    'Coaching staff',
    'Federation / NOC official',
    'Media',
    'Athlete Family / Friends',
    'Race Director',
    'Athlete Representatives',
    'Other'
  ]
};
<|endoftext|>


<commit_before>Hummingbird.CommunityWidgetComponent = Ember.Component.extend({
  classNames: ["community-widget-panel"],
  topics: [],

  loadPosts: function() {
    var self = this;

    ic.ajax({
      url: this.get('apiCall'),
      type: "GET"
    }).then(function(data) {

      var users = {},
          topics = [];

      data.users.forEach(function(user) {
        users[user.id] = user;
      });

      for (var i=0; i<data.topic_list.topics.length; i++) {
        var topicInfo = data.topic_list.topics[i],
            topic = {};

        if (topics.length === 5) break;
        if (topicInfo.pinned) continue;

        topic.title = topicInfo.title;
        topic.url = "http://forums.hummingbird.me/t/" + topicInfo.slug + "/" + topicInfo.id;
        if (topicInfo.last_read_post_number) {
          topic.url += "/" + topicInfo.last_read_post_number;
        }
        topic.postCount = topicInfo.highest_post_number;
        topic.lastPostTime = topicInfo.last_posted_at;

        topic.users = [];
        topicInfo.posters.forEach(function(poster) {
          var user = users[poster.user_id];
          topic.users.push({
            name: user.username,
            avatar: user.avatar_template.replace(/\.[a-zA-Z]+\?/, '.jpg?').replace("{size}", "small"),
            url: "http://forums.hummingbird.me/users/" + user.username + "/activity"
          });
        });

        topics.push(topic);
      }

      self.set('topics', topics);

    }, function() {
      console.log("Could not load " + self.get('apiCall'));
    });
  }.on('init')
});
<commit_msg>Revert "Remove "withCredentials"."<commit_after>Hummingbird.CommunityWidgetComponent = Ember.Component.extend({
  classNames: ["community-widget-panel"],
  topics: [],

  loadPosts: function() {
    var self = this;

    ic.ajax({
      url: this.get('apiCall'),
      type: "GET",
      xhrFields: {
        withCredentials: true
      }
    }).then(function(data) {

      var users = {},
          topics = [];

      data.users.forEach(function(user) {
        users[user.id] = user;
      });

      for (var i=0; i<data.topic_list.topics.length; i++) {
        var topicInfo = data.topic_list.topics[i],
            topic = {};

        if (topics.length === 5) break;
        if (topicInfo.pinned) continue;

        topic.title = topicInfo.title;
        topic.url = "http://forums.hummingbird.me/t/" + topicInfo.slug + "/" + topicInfo.id;
        if (topicInfo.last_read_post_number) {
          topic.url += "/" + topicInfo.last_read_post_number;
        }
        topic.postCount = topicInfo.highest_post_number;
        topic.lastPostTime = topicInfo.last_posted_at;

        topic.users = [];
        topicInfo.posters.forEach(function(poster) {
          var user = users[poster.user_id];
          topic.users.push({
            name: user.username,
            avatar: user.avatar_template.replace(/\.[a-zA-Z]+\?/, '.jpg?').replace("{size}", "small"),
            url: "http://forums.hummingbird.me/users/" + user.username + "/activity"
          });
        });

        topics.push(topic);
      }

      self.set('topics', topics);

    }, function() {
      console.log("Could not load " + self.get('apiCall'));
    });
  }.on('init')
});
<|endoftext|>


<commit_before>(function(window){

	var libraries = {},
		queues = {},
		loaded = [],
		blazedefer = function(){return blazedefer;};

	function HTTPRequest() {
		var xhr = new XMLHttpRequest,
			callbacks = [],
			isLoaded = function(){
				return xhr.readyState === 4 && xhr.status == 200;
			};

		xhr.onreadystatechange = function(){
			if(isLoaded()) {
				for(var i in callbacks) {
					callbacks[i].apply(this, callbacks[i].params);
				}
			}
		}

		xhr._onload = function(callback, params) {

			if(isLoaded()) {
				callback.apply(this, params)
			} else {
				callback.params = params
				callbacks.push(callback);
			}
		}

		return xhr;
	}
	/**
	 * Add onload listener to an element
	 */
	function elementReady(element, callback, param) {

		var fired = false,
			ready = function() {

				/**
				 * makes sure that the callback is only executed once
				 */
				if(!fired){
					fired =true;
					callback.apply(element, param);

				}
			},
			readyState = function(){
				if(el.readyState == "complete") {
					ready();
				}
			};

		if(element.addEventListener){
			element.addEventListener('DOMContentLoaded',ready);
			element.addEventListener('load',ready);
		}else{
			element.attachEvent('onreadystatechange',readyState);
			element.attachEvent('onload',ready);
		}

		return element;
	}

	/**
	 * create element base from the source
	 * @return DOMElment
	 */
	function createElement(source, name){
		var el;
		if( (/.js$/).test(source) ){
			el = document.createElement('script');
			el.src = source;
			el.async = true;
		}else{
			el = document.createElement('link');
			el.rel = "stylesheet";
			el.href = source;
		}

		/**
		 * set the name of the element
		 */
		if(name) {
			el.setAttribute('blazedefer-library', name);
		}

		return el;
	}

	/**
	 * extends object1 with the object2
	 */
	function extend(obj, obj2) {

		for(attr in obj2) {
			obj[attr] = obj2[attr];
		}

		return obj;
	}

	/**
	 * public methods of bladedefer
	 */
	var methods = {

		elementReady: elementReady,

		/**
		 * check url if its the same host with the window url
		 */
		isSameHost: function(url) {
			/**
			 * a nifty trick to have a complete web url
			 */
			 var a = document.createElement('a');
			 a.href = url;

			 return a.href.indexOf(window.location.host) > -1 ? true : false;
		},

	 	/**
		 * check if the script is already loaded
		 * @param string name
		 */
	 	isLoaded: function(name){
		 	return loaded.indexOf(name) > -1 ? true : false;
		},

		/**
		 * check if the scripts are already loaded
		 * @param Array scripts
		 */
		isAllLoaded: function(scripts){
			for(var i in scripts) {
				/**
				 * if one script is not loaded, immediately returns false
				 */
				if(!this.isLoaded(scripts[i])) {
					return false;
				}
			}

			return true;
		},
		/**
	  	 * add libraries to the listing
	  	 * @param object libs
	  	 */
		libraries: function(libs){
		 	extend(libraries, libs);
		 	return this;
		},

		/**
		 * execute a function when the dependencies are loaded
		 * @param array dependencies
		 * @param function callback
		 */
		run: function(dependencies, callback, param){

			/**
			 * initiate variable functions to be called later
			 */
			var self = this,
				callback = callback ? callback : function(){},
				dependencies = dependencies ? dependencies : [],
				reCheckDependency = function(){
					/**
					 * recheck dependencies if everything is already loaded
					 */
					 if(self.isAllLoaded(dependencies)) {
					 	callback.apply(this, param);
					 }
				},
				loadToDocument = function(url, scriptName){

					var firstQueue = false,
						docScript;
					/**
					 * check first if the script is already in queue
					 */
					 if(queues[scriptName]) {
					 	/**
					 	 * the script is already in the queue listing
					 	 * then just retreive its script tag instance
					 	 * and add "ready" event later
					 	 */
						docScript = queues[scriptName];
					 } else {

					 	docScript = createElement(url, scriptName);
					 	queues[scriptName] = docScript;
					 	firstQueue = true;
					 }

						/**
				 	 	  * add onload listener for the script tag
				 	 	  */

				 	 	 elementReady(docScript, function(){

				 	 	 	/**
				 	 	 	 * if the script is not yet in the list of loaded script
				 	 	 	 */
				 	 	 	if(!self.isLoaded(scriptName)) {
				 	 	 		loaded.push(scriptName);
				 	 	 	}
				 	 	 	/**
				 	 	 	 * remove the script from the queue list
				 	 	 	 */
				 	 	 	queues[scriptName] = null;

				 	 	 	reCheckDependency();

				 	 	 });

			 	 	 /**
			 	 	  * and now, add the script in the head section
			 	 	  */
			 	 	  if(firstQueue) {
			 	 	  	document.getElementsByTagName('head')[0].appendChild(docScript);
			 	 	  }

				};

			/**
			 * transform dependencies datatype into array
			 */
			 dependencies = dependencies.constructor == String ? [dependencies] : dependencies;

			 /**
			  * if doesnt have dependencies, then just run the callback immediately
			  */
			 if(!dependencies.length) {
			 	callback.apply(this, param);
			 }

			 /**
			  * added settimeout for a nonblocking rendering in the html
			  */
			 //setTimeout(function(){

			 	/**
				 * loop through each dependency script
				 */
				 for(var i in dependencies) {
				 	var scriptName = dependencies[i];

				 	/**
				 	 * check if the script is already loaded in the html
				 	 */
				 	 if(self.isLoaded(scriptName)) {
				 	 	reCheckDependency();

				 	 } else {
				 	 	/**
				 	 	 * check if the script name exists in the library listing
				 	 	 */
				 	 	var scriptData = libraries[scriptName];

				 	 	 /**
				 	 	  * stop and log error if the script doesnt exists in the list
				 	 	  */
				 	 	if(!scriptData) {
				 	 	 	return console.error("Script: "+scriptName+" doesn\'t exists in the library.");
				 	 	}

				 	 	 /**
				 	 	  * check if the data is a string or an object
				 	 	  * if string, transform it to object for uniform datatype
				 	 	  */
				 	 	scriptData = scriptData.constructor == String ? {url: scriptData} : scriptData;

				 	 	/**
				 	 	 * check for the dependency of the script
				 	 	 */
				 	 	if(scriptData.dependency) {

				 	 		var dependency = scriptData.dependency,
				 	 			scriptPreload = {
				 	 				available: false,
				 	 				requestDone: false,
				 	 				data: null
				 	 			};

				 	 	 	var executePreloadScript = function(_scriptPreload, _scriptData, name){
				 	 	 			console.log('try: '+name+' ' + _scriptPreload.requestDone);
				 	 				if(_scriptPreload.requestDone) {
				 	 					try {
				 	 						eval(_scriptPreload.data);

				 	 						if(!self.isLoaded(name)) {
								 	 	 		loaded.push(name);
								 	 	 	}
								 	 	 	queues[name] = null;

				 	 						console.log('evaluated', name);

				 	 						reCheckDependency();

				 	 					}catch(e) {
				 	 						console.error('Error: '+ _scriptData.url);
				 	 						console.error(e);
				 	 					}

				 	 				}

				 	 			};
				 	 		/**
				 	 		 * check and transform the datatype of dependency into array
				 	 		 */
				 	 		dependency = dependency.constructor == String ? [dependency] :  dependency;

				 	 		/**
				 	 		 * try to preload this current script
				 	 		 * check first if the script url is the same host as the window host
				 	 		 * so that we can above the CORS
				 	 		 */

				 	 		 if(self.isSameHost(scriptData.url) ) {

				 	 		 	scriptPreload.available = true;

				 	 		 	if(!queues[scriptName]) {
									console.log('preload: '+scriptName);

					 	 		 	var xhr = new HTTPRequest;
					 	 		 	xhr.open('GET', scriptData.url, true);

					 	 		 	xhr._onload( function(_scriptPreload, _xhr, _scriptData, name){

					 	 		 		if(_xhr.readyState === 4 && _xhr.status == 200) {
					 	 		 			_scriptPreload.data = _xhr.responseText;
					 	 		 			executePreloadScript(_scriptPreload, _scriptData, name);
					 	 		 			_scriptPreload.requestDone = true;
					 	 		 		}
					 	 		 	}, [scriptPreload, xhr, scriptData, scriptName]);

					 	 		 	queues[scriptName] = xhr;
					 	 		 	xhr.send();
				 	 		 	}

				 	 		 }

				 	 		self.run(dependency, function(data, name, _preload){
				 	 			/**
				 	 			 * check if this parent script is requested in the preload
				 	 			 */
				 	 			if(_preload.available) {
				 	 				console.log('available: '+name)
				 	 				executePreloadScript(_preload, data, name);
				 	 				_preload.requestDone = true;

				 	 			} else {
				 	 				loadToDocument(data.url, name);
				 	 			}

				 	 		},[scriptData, scriptName, scriptPreload]);

				 	 	} else {
					 	 	loadToDocument(scriptData.url, scriptName);
				 	 	}

				 	 }
				 }
			// });

			return this;
		},

		/**
		 * load js scripts
		 * @param Array dependencies: name of the libraries
		 * @param Array|String scripts: url
		 */
		loadScripts: function(dependecies, urls, callback, params){
			var _loaded = [];
			/**
			 * default value of urls is array
			 */
			urls = urls ? urls : [];

			/**
			 * check urls datatype
			 * and transform it to array
			 */
			 urls = urls.constructor == String ? [urls] : urls;

			 this.run(dependecies, function(){
			 	for(var i in urls) {
			 		var docScript = createElement(urls[i]);

			 		elementReady(docScript, function(i){
			 			_loaded.push(urls[i]);

			 			if(_loaded.length == urls.length) {
			 				callback.apply(blazedefer, params)
			 			}
			 		},[i]);

			 		document.getElementsByTagName('head')[0].appendChild(docScript);
			 	}
			 });

			return this;
		},

		/**
		 * defer css
		 */
		loaddCss: function(urls){
			self.loadScripts([], urls);
		}
	 }

	 window.blazedefer = extend(blazedefer, methods);

})(window);<commit_msg>append document script for the preloaded script<commit_after>(function(window){

	var libraries = {},
		queues = {},
		loaded = [],
		blazedefer = function(){return blazedefer;};

	function HTTPRequest() {
		var xhr = new XMLHttpRequest,
			callbacks = [],
			isLoaded = function(){
				return xhr.readyState === 4 && xhr.status == 200;
			};

		xhr.onreadystatechange = function(){
			if(isLoaded()) {
				for(var i in callbacks) {
					callbacks[i].apply(this, callbacks[i].params);
				}
			}
		}

		xhr._onload = function(callback, params) {

			if(isLoaded()) {
				callback.apply(this, params)
			} else {
				callback.params = params
				callbacks.push(callback);
			}
		}

		return xhr;
	}
	/**
	 * Add onload listener to an element
	 */
	function elementReady(element, callback, param) {

		var fired = false,
			ready = function() {

				/**
				 * makes sure that the callback is only executed once
				 */
				if(!fired){
					fired =true;
					callback.apply(element, param);

				}
			},
			readyState = function(){
				if(el.readyState == "complete") {
					ready();
				}
			};

		if(element.addEventListener){
			element.addEventListener('DOMContentLoaded',ready);
			element.addEventListener('load',ready);
		}else{
			element.attachEvent('onreadystatechange',readyState);
			element.attachEvent('onload',ready);
		}

		return element;
	}

	/**
	 * create element base from the source
	 * @return DOMElment
	 */
	function createElement(source, name){
		var el;
		if( (/.js$/).test(source) ){
			el = document.createElement('script');
			el.src = source;
			el.async = true;
		}else{
			el = document.createElement('link');
			el.rel = "stylesheet";
			el.href = source;
		}

		/**
		 * set the name of the element
		 */
		if(name) {
			el.setAttribute('blazedefer-library', name);
		}

		return el;
	}

	/**
	 * extends object1 with the object2
	 */
	function extend(obj, obj2) {

		for(attr in obj2) {
			obj[attr] = obj2[attr];
		}

		return obj;
	}

	/**
	 * public methods of bladedefer
	 */
	var methods = {

		elementReady: elementReady,

		/**
		 * check url if its the same host with the window url
		 */
		isSameHost: function(url) {
			/**
			 * a nifty trick to have a complete web url
			 */
			 var a = document.createElement('a');
			 a.href = url;

			 return a.href.indexOf(window.location.host) > -1 ? true : false;
		},

	 	/**
		 * check if the script is already loaded
		 * @param string name
		 */
	 	isLoaded: function(name){
		 	return loaded.indexOf(name) > -1 ? true : false;
		},

		/**
		 * check if the scripts are already loaded
		 * @param Array scripts
		 */
		isAllLoaded: function(scripts){
			for(var i in scripts) {
				/**
				 * if one script is not loaded, immediately returns false
				 */
				if(!this.isLoaded(scripts[i])) {
					return false;
				}
			}

			return true;
		},
		/**
	  	 * add libraries to the listing
	  	 * @param object libs
	  	 */
		libraries: function(libs){
		 	extend(libraries, libs);
		 	return this;
		},

		/**
		 * execute a function when the dependencies are loaded
		 * @param array dependencies
		 * @param function callback
		 */
		run: function(dependencies, callback, param){

			/**
			 * initiate variable functions to be called later
			 */
			var self = this,
				callback = callback ? callback : function(){},
				dependencies = dependencies ? dependencies : [],
				reCheckDependency = function(){
					/**
					 * recheck dependencies if everything is already loaded
					 */
					 if(self.isAllLoaded(dependencies)) {
					 	callback.apply(this, param);
					 }
				},
				loadToDocument = function(url, scriptName){

					var firstQueue = false,
						docScript;
					/**
					 * check first if the script is already in queue
					 */
					 if(queues[scriptName]) {
					 	/**
					 	 * the script is already in the queue listing
					 	 * then just retreive its script tag instance
					 	 * and add "ready" event later
					 	 */
						docScript = queues[scriptName];
					 } else {

					 	docScript = createElement(url, scriptName);
					 	queues[scriptName] = docScript;
					 	firstQueue = true;
					 }

						/**
				 	 	  * add onload listener for the script tag
				 	 	  */

				 	 	 elementReady(docScript, function(){

				 	 	 	/**
				 	 	 	 * if the script is not yet in the list of loaded script
				 	 	 	 */
				 	 	 	if(!self.isLoaded(scriptName)) {
				 	 	 		loaded.push(scriptName);
				 	 	 	}
				 	 	 	/**
				 	 	 	 * remove the script from the queue list
				 	 	 	 */
				 	 	 	queues[scriptName] = null;

				 	 	 	reCheckDependency();

				 	 	 });

			 	 	 /**
			 	 	  * and now, add the script in the head section
			 	 	  */
			 	 	  if(firstQueue) {
			 	 	  	document.getElementsByTagName('head')[0].appendChild(docScript);
			 	 	  }

				};

			/**
			 * transform dependencies datatype into array
			 */
			 dependencies = dependencies.constructor == String ? [dependencies] : dependencies;

			 /**
			  * if doesnt have dependencies, then just run the callback immediately
			  */
			 if(!dependencies.length) {
			 	callback.apply(this, param);
			 }

			 /**
			  * added settimeout for a nonblocking rendering in the html
			  */
			 //setTimeout(function(){

			 	/**
				 * loop through each dependency script
				 */
				 for(var i in dependencies) {
				 	var scriptName = dependencies[i];

				 	/**
				 	 * check if the script is already loaded in the html
				 	 */
				 	 if(self.isLoaded(scriptName)) {
				 	 	reCheckDependency();

				 	 } else {
				 	 	/**
				 	 	 * check if the script name exists in the library listing
				 	 	 */
				 	 	var scriptData = libraries[scriptName];

				 	 	 /**
				 	 	  * stop and log error if the script doesnt exists in the list
				 	 	  */
				 	 	if(!scriptData) {
				 	 	 	return console.error("Script: "+scriptName+" doesn\'t exists in the library.");
				 	 	}

				 	 	 /**
				 	 	  * check if the data is a string or an object
				 	 	  * if string, transform it to object for uniform datatype
				 	 	  */
				 	 	scriptData = scriptData.constructor == String ? {url: scriptData} : scriptData;

				 	 	/**
				 	 	 * check for the dependency of the script
				 	 	 */
				 	 	if(scriptData.dependency) {

				 	 		var dependency = scriptData.dependency,
				 	 			scriptPreload = {
				 	 				available: false,
				 	 				requestDone: false,
				 	 				data: null
				 	 			};

				 	 	 	var executePreloadScript = function(_scriptPreload, _scriptData, name){
				 	 	 			console.log('try: '+name+' ' + _scriptPreload.requestDone);
				 	 				if(_scriptPreload.requestDone) {
				 	 					try {
				 	 						window.eval(_scriptPreload.data);

				 	 						if(!self.isLoaded(name)) {
								 	 	 		loaded.push(name);
								 	 	 	}
								 	 	 	queues[name] = null;

								 	 	 	var docScript = document.createElement('script');
								 	 	 	docScript.setAttribute('data-src', _scriptData.url);

								 	 	 	if(name) {
								 	 	 		docScript.setAttribute('blazedefer-library', name);
								 	 	 	}
								 	 	 	document.getElementsByTagName('head')[0].appendChild(docScript);

				 	 						console.log('evaluated', name);

				 	 						reCheckDependency();

				 	 					}catch(e) {
				 	 						console.error('Error: '+ _scriptData.url);
				 	 						console.error(e);
				 	 					}

				 	 				}

				 	 			};
				 	 		/**
				 	 		 * check and transform the datatype of dependency into array
				 	 		 */
				 	 		dependency = dependency.constructor == String ? [dependency] :  dependency;

				 	 		/**
				 	 		 * try to preload this current script
				 	 		 * check first if the script url is the same host as the window host
				 	 		 * so that we can above the CORS
				 	 		 */

				 	 		 if(self.isSameHost(scriptData.url) ) {

				 	 		 	scriptPreload.available = true;

				 	 		 	if(!queues[scriptName]) {
									console.log('preload: '+scriptName);

					 	 		 	var xhr = new HTTPRequest;
					 	 		 	xhr.open('GET', scriptData.url, true);

					 	 		 	xhr._onload( function(_scriptPreload, _xhr, _scriptData, name){

					 	 		 		if(_xhr.readyState === 4 && _xhr.status == 200) {
					 	 		 			_scriptPreload.data = _xhr.responseText;
					 	 		 			executePreloadScript(_scriptPreload, _scriptData, name);
					 	 		 			_scriptPreload.requestDone = true;
					 	 		 		}
					 	 		 	}, [scriptPreload, xhr, scriptData, scriptName]);

					 	 		 	queues[scriptName] = xhr;
					 	 		 	xhr.send();
				 	 		 	}

				 	 		 }

				 	 		self.run(dependency, function(data, name, _preload){
				 	 			/**
				 	 			 * check if this parent script is requested in the preload
				 	 			 */
				 	 			if(_preload.available) {
				 	 				console.log('available: '+name)
				 	 				executePreloadScript(_preload, data, name);
				 	 				_preload.requestDone = true;

				 	 			} else {
				 	 				loadToDocument(data.url, name);
				 	 			}

				 	 		},[scriptData, scriptName, scriptPreload]);

				 	 	} else {
					 	 	loadToDocument(scriptData.url, scriptName);
				 	 	}

				 	 }
				 }
			// });

			return this;
		},

		/**
		 * load js scripts
		 * @param Array dependencies: name of the libraries
		 * @param Array|String scripts: url
		 */
		loadScripts: function(dependecies, urls, callback, params){
			var _loaded = [];
			/**
			 * default value of urls is array
			 */
			urls = urls ? urls : [];

			/**
			 * check urls datatype
			 * and transform it to array
			 */
			 urls = urls.constructor == String ? [urls] : urls;

			 this.run(dependecies, function(){
			 	for(var i in urls) {
			 		var docScript = createElement(urls[i]);

			 		elementReady(docScript, function(i){
			 			_loaded.push(urls[i]);

			 			if(_loaded.length == urls.length) {
			 				callback.apply(blazedefer, params)
			 			}
			 		},[i]);

			 		document.getElementsByTagName('head')[0].appendChild(docScript);
			 	}
			 });

			return this;
		},

		/**
		 * defer css
		 */
		loaddCss: function(urls){
			self.loadScripts([], urls);
		}
	 }

	 window.blazedefer = extend(blazedefer, methods);

})(window);<|endoftext|>