<commit_before>import angular from 'angular';
import _ from 'underscore';

angular.module('dimApp')
  .factory('dimDestinyTrackerService', DestinyTrackerService);

class gunTransformer {
  translateToDtrGun(gun) {
    return {
      referenceId: gun.hash,
      roll: this.getDtrRoll(gun)
    };
  }

  getRollAndPerks(gun) {
    return {
      roll: this.getDtrRoll(gun),
      selectedPerks: this.getDtrPerks(gun),
      referenceId: gun.hash,
      instanceId: gun.id,
    };
  }

  getDtrPerks(gun) {
    if (!gun.talentGrid) {
      return null;
    }

    return gun.talentGrid.dtrPerks;
  }

  getDtrRoll(gun) {
    if (!gun.talentGrid) {
      return null;
    }

    return gun.talentGrid.dtrRoll;
  }
}

class ScoreMaintainer {
  constructor() {
    this._gunTransformer = new gunTransformer();
    this._itemStores = [];
  }

  getMatchingItem(item) {
    var dtrItem = this._gunTransformer.translateToDtrGun(item);

    dtrItem.referenceId = String(dtrItem.referenceId);

    return _.findWhere(this._itemStores, { referenceId: dtrItem.referenceId, roll: dtrItem.roll });
  }

  getRatingData(item) {
    var matchingItem = this.getMatchingItem(item);

    if (!matchingItem) {
      return null;
    }

    return matchingItem;
  }

  toAtMostOneDecimal(rating) {
    if (!rating) {
      return null;
    }

    if ((rating % 1) === 0) {
      return rating;
    }

    return rating.toFixed(1);
  }

  addScore(dtrRating) {
    dtrRating.rating = this.toAtMostOneDecimal(dtrRating.rating);

    this._itemStores.push(dtrRating);
  }

  addUserReviewData(item,
                    userReview) {
    var dtrItem = this._gunTransformer.translateToDtrGun(item);
    var matchingItem = this.getMatchingItem(dtrItem);

    var rating = matchingItem.rating;

    Object.assign(matchingItem,
                  userReview);

    matchingItem.rating = rating;
  }

  addReviewsData(item,
                 reviewsData) {
    var matchingItem = this.getMatchingItem(item);

    matchingItem.reviews = reviewsData.reviews;
    matchingItem.reviewsDataFetched = true;
  }

  getItemStores() {
    return this._itemStores;
  }
}

class gunListBuilder {
  constructor() {
    this._gunTransformer = new gunTransformer();
  }

  getNewItems(allItems, scoreMaintainer) {
    var self = this;
    var allDtrItems = _.map(allItems, function(item) { return self._gunTransformer.translateToDtrGun(item); });
    var allKnownDtrItems = scoreMaintainer.getItemStores();

    var unmatched = _.filter(allDtrItems, function(dtrItem) {
      var matchingItem = _.findWhere(allKnownDtrItems, { referenceId: String(dtrItem.referenceId), roll: dtrItem.roll });
      return (matchingItem === null);
    });

    return unmatched;
  }

  getAllItems(stores) {
    var allItems = [];

    stores.forEach(function(store) {
      allItems = allItems.concat(store.items);
    });

    return allItems;
  }

  getGuns(stores, scoreMaintainer) {
    var self = this;
    var allItems = this.getAllItems(stores);

    var allGuns = _.filter(allItems,
                        function(item) {
                          if (!item.primStat) {
                            return false;
                          }

                          return (item.primStat.statHash === 368428387);
                        });

    var newGuns = this.getNewItems(allGuns, scoreMaintainer);

    if (scoreMaintainer.getItemStores().length > 0) {
      return newGuns;
    }

    return _.map(allGuns, function(item) { return self._gunTransformer.translateToDtrGun(item); });
  }

  getWeaponList(stores, scoreMaintainer) {
    var guns = this.getGuns(stores, scoreMaintainer);

    var list = [];
    var self = this;

    guns.forEach(function(gun) {
      if (!self.isKnownGun(list, gun)) {
        list.push(gun);
      }
    });

    return list;
  }

  isKnownGun(list, dtrGun) {
    return _.contains(list, dtrGun);
  }
}

class trackerErrorHandler {
  constructor($q,
              $translate) {
    this.$q = $q;
    this.$translate = $translate;
  }

  handleErrors(response) {
    if (response.status !== 200) {
      return this.$q.reject(new Error(this.$translate.instant('DtrReview.ServiceCallError')));
    }

    return response;
  }

  handleSubmitErrors(response) {
    if (response.status !== 204) {
      return this.$q.reject(new Error(this.$translate.instant('DtrReview.ServiceSubmitError')));
    }

    return response;
  }
}

class bulkFetcher {
  constructor($q, $http, trackerErrorHandler, loadingTracker, scoreMaintainer) {
    this.$q = $q;
    this.$http = $http;
    this._gunListBuilder = new gunListBuilder();
    this._trackerErrorHandler = trackerErrorHandler;
    this._loadingTracker = loadingTracker;
    this._scoreMaintainer = scoreMaintainer;
  }

  getBulkWeaponDataPromise(gunList) {
    return {
      method: 'POST',
      url: 'https://reviews-api.destinytracker.net/api/weaponChecker/fetch',
      data: gunList,
      dataType: 'json'
    };
  }

  getBulkFetchPromise(stores) {
    if (stores.stores.length === 0) {
      return this.$q.resolve();
    }

    var weaponList = this._gunListBuilder.getWeaponList(stores.stores, this._scoreMaintainer);

    if (!weaponList.length) {
      return this.$q.resolve();
    }

    var promise = this.$q
              .when(this.getBulkWeaponDataPromise(weaponList))
              .then(this.$http)
              .then(this._trackerErrorHandler.handleErrors, this._trackerErrorHandler.handleErrors)
              .then((response) => { return response.data; });

    this._loadingTracker.addPromise(promise);

    return promise;
  }

  bulkFetch(stores) {
    this.getBulkFetchPromise(stores)
      .then((bulkRankings) => this.attachRankings(bulkRankings,
                                                  stores.stores));
  }

  attachRankings(bulkRankings,
                 stores) {
    if ((!bulkRankings) &&
        (!stores)) {
      return;
    }

    var self = this;

    if (bulkRankings) {
      bulkRankings.forEach(function(bulkRanking) {
        self._scoreMaintainer.addScore(bulkRanking);
      });
    }

    stores.forEach(function(store) {
      store.items.forEach(function(storeItem) {
        var matchingItem = self._scoreMaintainer.getRatingData(storeItem);

        if (matchingItem) {
          storeItem.dtrRating = matchingItem.rating;
          storeItem.userRating = matchingItem.rating;
          storeItem.userReview = matchingItem.review;
          storeItem.pros = matchingItem.pros;
          storeItem.cons = matchingItem.cons;
        }
      });
    });
  }
}

class reviewsFetcher {
  constructor($q, $http, trackerErrorHandler, loadingTracker, scoreMaintainer) {
    this.$q = $q;
    this.$http = $http;
    this._gunTransformer = new gunTransformer();
    this._trackerErrorHandler = trackerErrorHandler;
    this._loadingTracker = loadingTracker;
    this._scoreMaintainer = scoreMaintainer;
  }

  getItemReviewsCall(item) {
    return {
      method: 'POST',
      url: 'https://reviews-api.destinytracker.net/api/weaponChecker/reviews',
      data: item,
      dataType: 'json'
    };
  }

  getItemReviewsPromise(item) {
    var postWeapon = this._gunTransformer.getRollAndPerks(item);

    var promise = this.$q
              .when(this.getItemReviewsCall(postWeapon))
              .then(this.$http)
              .then(this._trackerErrorHandler.handleErrors, this._trackerErrorHandler.handleErrors)
              .then((response) => { return response.data; });

    this._loadingTracker.addPromise(promise);

    return promise;
  }

  getUserReview(reviewData) {
    return _.findWhere(reviewData.reviews, { isReviewer: true });
  }

  attachReviews(item,
                reviewData) {
    var userReview = this.getUserReview(reviewData);

    item.communityReviews = reviewData.reviews;

    if (userReview) {
      item.userRating = userReview.rating;
      item.userReview = userReview.review;
      item.userReviewPros = userReview.pros;
      item.userReviewCons = userReview.cons;
    }

    this._scoreMaintainer.addReviewsData(item,
                                         reviewData);
  }

  getItemReviews(item) {
    var ratingData = this._scoreMaintainer.getRatingData(item);
    if (ratingData.reviewsDataFetched) {
      this.attachReviews(item,
                         ratingData);

      return;
    }

    this.getItemReviewsPromise(item)
      .then((data) => this.attachReviews(item,
                                         data));
  }
}

class reviewSubmitter {
  constructor($q, $http, dimPlatformService, trackerErrorHandler, loadingTracker) {
    this.$q = $q;
    this.$http = $http;
    this._gunTransformer = new gunTransformer();
    this._trackerErrorHandler = trackerErrorHandler;
    this._dimPlatformService = dimPlatformService;
    this._loadingTracker = loadingTracker;
  }

  getReviewer() {
    var membershipInfo = this._dimPlatformService.getActive();

    return {
      membershipId: membershipInfo.membershipId,
      type: membershipInfo.type,
      displayName: membershipInfo.id
    };
  }

  toRatingAndReview(userReview) {
    return {
      rating: userReview.rating,
      review: userReview.review,
      pros: userReview.pros,
      cons: userReview.cons
    };
  }

  submitItemReviewCall(itemReview) {
    return {
      method: 'POST',
      url: 'https://reviews-api.destinytracker.net/api/weaponChecker/reviews/submit',
      data: itemReview,
      dataType: 'json'
    };
  }

  submitReviewPromise(item, userReview) {
    var rollAndPerks = this._gunTransformer.getRollAndPerks(item);
    var reviewer = this.getReviewer();
    var review = this.toRatingAndReview(userReview);

    var rating = Object.assign(rollAndPerks, review);
    rating.reviewer = reviewer;

    var promise = this.$q
              .when(this.submitItemReviewCall(rating))
              .then(this.$http)
              .then(this._trackerErrorHandler.handleSubmitErrors, this._trackerErrorHandler.handleSubmitErrors);

    this._loadingTracker.addPromise(promise);

    return promise;
  }

  submitReview(item, userReview) {
    this.submitReviewPromise(item, userReview);
  }
}

function DestinyTrackerService($q,
                               $http,
                               $rootScope,
                               dimPlatformService,
                               dimSettingsService,
                               $translate,
                               dimFeatureFlags,
                               loadingTracker) {
  var _scoreMaintainer = new ScoreMaintainer();
  var _trackerErrorHandler = new trackerErrorHandler($q, $translate);
  var _bulkFetcher = new bulkFetcher($q, $http, _trackerErrorHandler, loadingTracker, _scoreMaintainer);
  var _reviewsFetcher = new reviewsFetcher($q, $http, _trackerErrorHandler, loadingTracker, _scoreMaintainer);
  var _reviewSubmitter = new reviewSubmitter($q, $http, dimPlatformService, _trackerErrorHandler, loadingTracker);
  var _postEnabled = dimFeatureFlags.sendingWeaponDataEnabled;

  function _userHasNotOkayedPostingIds() {
    return (!dimSettingsService.allowIdPostToDtr);
  }

  $rootScope.$on('item-clicked', function(event, item) {
    if ((!_postEnabled) ||
        (_userHasNotOkayedPostingIds())) {
      return;
    }

    _reviewsFetcher.getItemReviews(item);
  });

  $rootScope.$on('dim-stores-updated', function(event, stores) {
    if (!_postEnabled) {
      return;
    }

    _bulkFetcher.bulkFetch(stores);
  });

  $rootScope.$on('review-submitted', function(event, item, userReview) {
    if ((!_postEnabled) ||
        (_userHasNotOkayedPostingIds())) {
      return;
    }

    // TODO: bake item cache flush into submit promise
    _reviewSubmitter.submitReview(item, userReview);
  });

  return {
    init: function() {},
    reattachScores: function(stores) {
      _bulkFetcher.attachRankings(null,
                                  stores);
    },
    updateUserRankings: function(item,
                                 userReview) {
      _scoreMaintainer.addUserReviewData(item,
                                         userReview);
    }
  };
}<commit_msg>Cache invalidation.<commit_after>import angular from 'angular';
import _ from 'underscore';

angular.module('dimApp')
  .factory('dimDestinyTrackerService', DestinyTrackerService);

class gunTransformer {
  translateToDtrGun(gun) {
    return {
      referenceId: gun.hash,
      roll: this.getDtrRoll(gun)
    };
  }

  getRollAndPerks(gun) {
    return {
      roll: this.getDtrRoll(gun),
      selectedPerks: this.getDtrPerks(gun),
      referenceId: gun.hash,
      instanceId: gun.id,
    };
  }

  getDtrPerks(gun) {
    if (!gun.talentGrid) {
      return null;
    }

    return gun.talentGrid.dtrPerks;
  }

  getDtrRoll(gun) {
    if (!gun.talentGrid) {
      return null;
    }

    return gun.talentGrid.dtrRoll;
  }
}

class ScoreMaintainer {
  constructor() {
    this._gunTransformer = new gunTransformer();
    this._itemStores = [];
  }

  getMatchingItem(item) {
    var dtrItem = this._gunTransformer.translateToDtrGun(item);

    dtrItem.referenceId = String(dtrItem.referenceId);

    return _.findWhere(this._itemStores, { referenceId: dtrItem.referenceId, roll: dtrItem.roll });
  }

  getRatingData(item) {
    var matchingItem = this.getMatchingItem(item);

    if (!matchingItem) {
      return null;
    }

    return matchingItem;
  }

  toAtMostOneDecimal(rating) {
    if (!rating) {
      return null;
    }

    if ((rating % 1) === 0) {
      return rating;
    }

    return rating.toFixed(1);
  }

  addScore(dtrRating) {
    dtrRating.rating = this.toAtMostOneDecimal(dtrRating.rating);

    this._itemStores.push(dtrRating);
  }

  addUserReviewData(item,
                    userReview) {
    var dtrItem = this._gunTransformer.translateToDtrGun(item);
    var matchingItem = this.getMatchingItem(dtrItem);

    var rating = matchingItem.rating;

    Object.assign(matchingItem,
                  userReview);

    matchingItem.rating = rating;
  }

  addReviewsData(item,
                 reviewsData) {
    var matchingItem = this.getMatchingItem(item);

    matchingItem.reviews = reviewsData.reviews;
    matchingItem.reviewsDataFetched = true;

    this.eventuallyPurgeCachedData(item);
  }

  getItemStores() {
    return this._itemStores;
  }

  eventuallyPurgeCachedData(item) {
    var tenMinutes = 1000 * 60 * 10;
    var self = this;

    setTimeout(function() {
      var matchingItem = self.getMatchingItem(item);

      matchingItem.reviews = null;
      matchingItem.reviewsDataFetched = false;
    },
      tenMinutes);
  }
}

class gunListBuilder {
  constructor() {
    this._gunTransformer = new gunTransformer();
  }

  getNewItems(allItems, scoreMaintainer) {
    var self = this;
    var allDtrItems = _.map(allItems, function(item) { return self._gunTransformer.translateToDtrGun(item); });
    var allKnownDtrItems = scoreMaintainer.getItemStores();

    var unmatched = _.filter(allDtrItems, function(dtrItem) {
      var matchingItem = _.findWhere(allKnownDtrItems, { referenceId: String(dtrItem.referenceId), roll: dtrItem.roll });
      return (matchingItem === null);
    });

    return unmatched;
  }

  getAllItems(stores) {
    var allItems = [];

    stores.forEach(function(store) {
      allItems = allItems.concat(store.items);
    });

    return allItems;
  }

  getGuns(stores, scoreMaintainer) {
    var self = this;
    var allItems = this.getAllItems(stores);

    var allGuns = _.filter(allItems,
                        function(item) {
                          if (!item.primStat) {
                            return false;
                          }

                          return (item.primStat.statHash === 368428387);
                        });

    var newGuns = this.getNewItems(allGuns, scoreMaintainer);

    if (scoreMaintainer.getItemStores().length > 0) {
      return newGuns;
    }

    return _.map(allGuns, function(item) { return self._gunTransformer.translateToDtrGun(item); });
  }

  getWeaponList(stores, scoreMaintainer) {
    var guns = this.getGuns(stores, scoreMaintainer);

    var list = [];
    var self = this;

    guns.forEach(function(gun) {
      if (!self.isKnownGun(list, gun)) {
        list.push(gun);
      }
    });

    return list;
  }

  isKnownGun(list, dtrGun) {
    return _.contains(list, dtrGun);
  }
}

class trackerErrorHandler {
  constructor($q,
              $translate) {
    this.$q = $q;
    this.$translate = $translate;
  }

  handleErrors(response) {
    if (response.status !== 200) {
      return this.$q.reject(new Error(this.$translate.instant('DtrReview.ServiceCallError')));
    }

    return response;
  }

  handleSubmitErrors(response) {
    if (response.status !== 204) {
      return this.$q.reject(new Error(this.$translate.instant('DtrReview.ServiceSubmitError')));
    }

    return response;
  }
}

class bulkFetcher {
  constructor($q, $http, trackerErrorHandler, loadingTracker, scoreMaintainer) {
    this.$q = $q;
    this.$http = $http;
    this._gunListBuilder = new gunListBuilder();
    this._trackerErrorHandler = trackerErrorHandler;
    this._loadingTracker = loadingTracker;
    this._scoreMaintainer = scoreMaintainer;
  }

  getBulkWeaponDataPromise(gunList) {
    return {
      method: 'POST',
      url: 'https://reviews-api.destinytracker.net/api/weaponChecker/fetch',
      data: gunList,
      dataType: 'json'
    };
  }

  getBulkFetchPromise(stores) {
    if (stores.stores.length === 0) {
      return this.$q.resolve();
    }

    var weaponList = this._gunListBuilder.getWeaponList(stores.stores, this._scoreMaintainer);

    if (!weaponList.length) {
      return this.$q.resolve();
    }

    var promise = this.$q
              .when(this.getBulkWeaponDataPromise(weaponList))
              .then(this.$http)
              .then(this._trackerErrorHandler.handleErrors, this._trackerErrorHandler.handleErrors)
              .then((response) => { return response.data; });

    this._loadingTracker.addPromise(promise);

    return promise;
  }

  bulkFetch(stores) {
    this.getBulkFetchPromise(stores)
      .then((bulkRankings) => this.attachRankings(bulkRankings,
                                                  stores.stores));
  }

  attachRankings(bulkRankings,
                 stores) {
    if ((!bulkRankings) &&
        (!stores)) {
      return;
    }

    var self = this;

    if (bulkRankings) {
      bulkRankings.forEach(function(bulkRanking) {
        self._scoreMaintainer.addScore(bulkRanking);
      });
    }

    stores.forEach(function(store) {
      store.items.forEach(function(storeItem) {
        var matchingItem = self._scoreMaintainer.getRatingData(storeItem);

        if (matchingItem) {
          storeItem.dtrRating = matchingItem.rating;
          storeItem.userRating = matchingItem.rating;
          storeItem.userReview = matchingItem.review;
          storeItem.pros = matchingItem.pros;
          storeItem.cons = matchingItem.cons;
        }
      });
    });
  }
}

class reviewsFetcher {
  constructor($q, $http, trackerErrorHandler, loadingTracker, scoreMaintainer) {
    this.$q = $q;
    this.$http = $http;
    this._gunTransformer = new gunTransformer();
    this._trackerErrorHandler = trackerErrorHandler;
    this._loadingTracker = loadingTracker;
    this._scoreMaintainer = scoreMaintainer;
  }

  getItemReviewsCall(item) {
    return {
      method: 'POST',
      url: 'https://reviews-api.destinytracker.net/api/weaponChecker/reviews',
      data: item,
      dataType: 'json'
    };
  }

  getItemReviewsPromise(item) {
    var postWeapon = this._gunTransformer.getRollAndPerks(item);

    var promise = this.$q
              .when(this.getItemReviewsCall(postWeapon))
              .then(this.$http)
              .then(this._trackerErrorHandler.handleErrors, this._trackerErrorHandler.handleErrors)
              .then((response) => { return response.data; });

    this._loadingTracker.addPromise(promise);

    return promise;
  }

  getUserReview(reviewData) {
    return _.findWhere(reviewData.reviews, { isReviewer: true });
  }

  attachReviews(item,
                reviewData) {
    var userReview = this.getUserReview(reviewData);

    item.communityReviews = reviewData.reviews;

    if (userReview) {
      item.userRating = userReview.rating;
      item.userReview = userReview.review;
      item.userReviewPros = userReview.pros;
      item.userReviewCons = userReview.cons;
    }

    this._scoreMaintainer.addReviewsData(item,
                                         reviewData);
  }

  getItemReviews(item) {
    var ratingData = this._scoreMaintainer.getRatingData(item);
    if (ratingData.reviewsDataFetched) {
      this.attachReviews(item,
                         ratingData);

      return;
    }

    this.getItemReviewsPromise(item)
      .then((data) => this.attachReviews(item,
                                         data));
  }
}

class reviewSubmitter {
  constructor($q, $http, dimPlatformService, trackerErrorHandler, loadingTracker, scoreMaintainer) {
    this.$q = $q;
    this.$http = $http;
    this._gunTransformer = new gunTransformer();
    this._trackerErrorHandler = trackerErrorHandler;
    this._dimPlatformService = dimPlatformService;
    this._loadingTracker = loadingTracker;
    this._scoreMaintainer = scoreMaintainer;
  }

  getReviewer() {
    var membershipInfo = this._dimPlatformService.getActive();

    return {
      membershipId: membershipInfo.membershipId,
      type: membershipInfo.type,
      displayName: membershipInfo.id
    };
  }

  toRatingAndReview(userReview) {
    return {
      rating: userReview.rating,
      review: userReview.review,
      pros: userReview.pros,
      cons: userReview.cons
    };
  }

  submitItemReviewCall(itemReview) {
    return {
      method: 'POST',
      url: 'https://reviews-api.destinytracker.net/api/weaponChecker/reviews/submit',
      data: itemReview,
      dataType: 'json'
    };
  }

  submitReviewPromise(item, userReview) {
    var rollAndPerks = this._gunTransformer.getRollAndPerks(item);
    var reviewer = this.getReviewer();
    var review = this.toRatingAndReview(userReview);

    var rating = Object.assign(rollAndPerks, review);
    rating.reviewer = reviewer;

    var promise = this.$q
              .when(this.submitItemReviewCall(rating))
              .then(this.$http)
              .then(this._trackerErrorHandler.handleSubmitErrors, this._trackerErrorHandler.handleSubmitErrors);

    this._loadingTracker.addPromise(promise);

    return promise;
  }

  eventuallyPurgeCachedData(item) {
    this._scoreMaintainer.eventuallyPurgeCachedData(item);
  }

  submitReview(item, userReview) {
    this.submitReviewPromise(item, userReview)
      .then(this.eventuallyPurgeCachedData(item));
  }
}

function DestinyTrackerService($q,
                               $http,
                               $rootScope,
                               dimPlatformService,
                               dimSettingsService,
                               $translate,
                               dimFeatureFlags,
                               loadingTracker) {
  var _scoreMaintainer = new ScoreMaintainer();
  var _trackerErrorHandler = new trackerErrorHandler($q, $translate);
  var _bulkFetcher = new bulkFetcher($q, $http, _trackerErrorHandler, loadingTracker, _scoreMaintainer);
  var _reviewsFetcher = new reviewsFetcher($q, $http, _trackerErrorHandler, loadingTracker, _scoreMaintainer);
  var _reviewSubmitter = new reviewSubmitter($q, $http, dimPlatformService, _trackerErrorHandler, loadingTracker, _scoreMaintainer);
  var _postEnabled = dimFeatureFlags.sendingWeaponDataEnabled;

  function _userHasNotOkayedPostingIds() {
    return (!dimSettingsService.allowIdPostToDtr);
  }

  $rootScope.$on('item-clicked', function(event, item) {
    if ((!_postEnabled) ||
        (_userHasNotOkayedPostingIds())) {
      return;
    }

    _reviewsFetcher.getItemReviews(item);
  });

  $rootScope.$on('dim-stores-updated', function(event, stores) {
    if (!_postEnabled) {
      return;
    }

    _bulkFetcher.bulkFetch(stores);
  });

  $rootScope.$on('review-submitted', function(event, item, userReview) {
    if ((!_postEnabled) ||
        (_userHasNotOkayedPostingIds())) {
      return;
    }

    _reviewSubmitter.submitReview(item, userReview);
  });

  return {
    init: function() {},
    reattachScores: function(stores) {
      _bulkFetcher.attachRankings(null,
                                  stores);
    },
    updateUserRankings: function(item,
                                 userReview) {
      _scoreMaintainer.addUserReviewData(item,
                                         userReview);
    }
  };
}<|endoftext|>


<commit_before>var communityStates = function(stateProvider) {
  stateProvider
  .state('community', {
    abstract: true,
    url: '/c/:community',
    parent: 'main',
    views: {
      "main": {
        template: "

"
      },
    },
    resolve: {
      community: ['Community', '$stateParams', '$rootScope', function(Community, $stateParams, $rootScope) {
        var promise = Community.get({id: $stateParams.community}).$promise;
        promise.then(function(community) {
          $rootScope.community = community;
        });
        return promise;
      }]
    }
  })
  .state('community.home', {
    abstract: true,
    parent: 'community',
    views: {
      community: {
        templateUrl: '/ui/community/base.tpl.html',
        controller: 'CommunityCtrl'
      }
    }
  })
  .state('community.seeds', {
    url: '',
    parent: 'community.home',
    views: {
      tab: {
        templateUrl: '/ui/community/seeds.tpl.html',
        controller: 'CommunitySeedsCtrl'
      }
    }
  })
  .state('community.about', {
    url: '/about',
    parent: 'community.home',
    views: {
      tab: {
        templateUrl: '/ui/community/about.tpl.html',
        controller: 'CommunityAboutCtrl'
      }
    }
  })
  .state('community.members', {
    url: '/members',
    parent: 'community.home',
    views: {
      tab: {
        templateUrl: '/ui/community/members.tpl.html',
        controller: 'CommunityMembersCtrl'
      }
    }
  })
  .state('createCommunity', {
    url: '/create/community',
    parent: 'main',
    views: {
      "main": {
        templateUrl: '/ui/community/createCommunity.tpl.html',
        controller: 'CreateCommunityCtrl'
      }
    }
  })
  .state('community.settings', {
    url: '/settings',
    views: {
      community: {
        templateUrl: '/ui/community/settings.tpl.html',
        controller: 'CommunitySettingsCtrl'
      }
    }
  })
  .state('community.newSeed', {
    url: '/new-seed',
    views: {
      "community": {
        templateUrl: '/ui/seeds/edit.tpl.html',
        controller: 'SeedEditCtrl'
      }
    },
    resolve: {
      seed: function() { return null; }
    }
  })
  .state('community.editSeed', {
    url: '/s/:seedId/edit',
    views: {
      community: {
        templateUrl: '/ui/seeds/edit.tpl.html',
        controller: 'SeedEditCtrl'
      }
    },
    resolve: {
      seed: ['Seed', '$stateParams', function(Seed, $stateParams) {
        return Seed.get({id: $stateParams.seedId}).$promise;
      }]
    }

  })
  .state('seed', {
    url: '/s/:seedId',
    parent: 'community',
    views: {
      "community": {
        templateUrl: '/ui/seeds/show.tpl.html',
        controller: 'SeedCtrl'
      }
    },
    resolve: {
      seed: ['Seed', '$stateParams', function(Seed, $stateParams) {
        return Seed.get({id: $stateParams.seedId}).$promise;
      }]
    },
    data: {
      singlePost: true
    }
  })
  .state('community.search', {
    url: "/search?q",
    resolve: {
      query: ['$stateParams', function($stateParams) {
        return $stateParams.q;
      }]
    },
    views: {
      "community": {
        templateUrl: '/ui/app/search.tpl.html',
        controller: 'SearchCtrl'
      }
    }
  });

};

var profileStates = function(stateProvider) {
  stateProvider
  .state('profile', {
    parent: 'main',
    url: '/u/:id',
    resolve: {
      editable: ['currentUser', '$stateParams', function(currentUser, $stateParams) {
        return parseInt(currentUser.id) == parseInt($stateParams.id);
      }],
      user: ['User', 'editable', '$stateParams', 'currentUser', function(User, editable, $stateParams, currentUser) {
        if (editable) {
          return currentUser;
        } else {
          return User.get({id: $stateParams.id}).$promise;
        }
      }],
      posts: ['user', function(user) {
        return user.seeds().$promise;
      }]
    },
    views: {
      'main': {
        templateUrl: '/ui/profile/base.tpl.html',
        controller: 'ProfileCtrl'
      }
    }
  })
  .state('profile.contributions', {
    url: '/contributions',
    resolve: {
      contributions: ['user', function(user) {
        return user.contributions().$promise;
      }]
    },
    views: {
      'tab': {
        templateUrl: '/ui/profile/contributions.tpl.html',
        controller: 'ProfileContributionsCtrl'
      }
    }
  })
  .state('profile.thanks', {
    url: '/thanks',
    resolve: {
      thanks: ['user', function(user) {
        return user.thanks().$promise;
      }]
    },
    views: {
      'tab': {
        templateUrl: '/ui/profile/thanks.tpl.html',
        controller: 'ProfileThanksCtrl'
      }
    }
  })
  .state('editProfile', {
    url: '/edit-profile',
    parent: 'main',
    views: {
      'main': {
        templateUrl: '/ui/profile/edit.tpl.html',
        controller: 'ProfileEditCtrl'
      }
    }
  });
}

var dependencies = ['$stateProvider', '$urlRouterProvider'];
dependencies.push(function($stateProvider, $urlRouterProvider) {

  $urlRouterProvider.otherwise(function($injector, $location){
    var state = $injector.get('$state');
    Rollbar.warning("404 Error: " + $location.path());
    state.go('404');
    return $location.path();
  });

  $stateProvider
    .state("404", {
      templateUrl: '/ui/app/404.tpl.html'
    })
    .state('main', {
      abstract: true,
      template: "

",
      resolve: {
        // Use the resource to fetch data from the server
        oldCurrentUser: ['CurrentUser', function(CurrentUser) {
          return CurrentUser.get().$promise;
        }],
        // user info has to be fetched from the new API for editing on
        // the new profile. eventually the old CurrentUser will be replaced
        // with this one.
        currentUser: ['User', function(User) {
          return User.current().$promise;
        }]
      },
      controller: ['$rootScope', 'oldCurrentUser', '$stateParams', function($rootScope, oldCurrentUser, $stateParams) {
        $rootScope.currentUser = oldCurrentUser;
      }]
    })
    .state('home', {
      url: '/',
      // redirects to hylo-play which resolves the URL to a community.
      onEnter: function reloadCtrl() {
        window.location.replace('/');
      }
    })
    .state('userSettings', {
      url: '/settings',
      parent: 'main',
      views: {
        "main": {
          templateUrl: '/ui/user/settings.tpl.html',
          controller: 'UserSettingsCtrl'
        }
      }
    })
    .state('network', {
      url: "/n/:network",
      views: {
        "": {
          templateUrl: '/ui/app/network.tpl.html',
          controller: 'NetworkCtrl'
        }
      }
    });

    communityStates($stateProvider);
    profileStates($stateProvider);

});

module.exports = dependencies;<commit_msg>handle old single-post links<commit_after>var communityStates = function(stateProvider) {
  stateProvider
  .state('community', {
    abstract: true,
    url: '/c/:community',
    parent: 'main',
    views: {
      "main": {
        template: "

"
      },
    },
    resolve: {
      community: ['Community', '$stateParams', '$rootScope', function(Community, $stateParams, $rootScope) {
        var promise = Community.get({id: $stateParams.community}).$promise;
        promise.then(function(community) {
          $rootScope.community = community;
        });
        return promise;
      }]
    }
  })
  .state('community.home', {
    abstract: true,
    parent: 'community',
    views: {
      community: {
        templateUrl: '/ui/community/base.tpl.html',
        controller: 'CommunityCtrl'
      }
    }
  })
  .state('community.seeds', {
    url: '',
    parent: 'community.home',
    views: {
      tab: {
        templateUrl: '/ui/community/seeds.tpl.html',
        controller: 'CommunitySeedsCtrl'
      }
    }
  })
  .state('community.about', {
    url: '/about',
    parent: 'community.home',
    views: {
      tab: {
        templateUrl: '/ui/community/about.tpl.html',
        controller: 'CommunityAboutCtrl'
      }
    }
  })
  .state('community.members', {
    url: '/members',
    parent: 'community.home',
    views: {
      tab: {
        templateUrl: '/ui/community/members.tpl.html',
        controller: 'CommunityMembersCtrl'
      }
    }
  })
  .state('createCommunity', {
    url: '/create/community',
    parent: 'main',
    views: {
      "main": {
        templateUrl: '/ui/community/createCommunity.tpl.html',
        controller: 'CreateCommunityCtrl'
      }
    }
  })
  .state('community.settings', {
    url: '/settings',
    views: {
      community: {
        templateUrl: '/ui/community/settings.tpl.html',
        controller: 'CommunitySettingsCtrl'
      }
    }
  })
  .state('community.newSeed', {
    url: '/new-seed',
    views: {
      "community": {
        templateUrl: '/ui/seeds/edit.tpl.html',
        controller: 'SeedEditCtrl'
      }
    },
    resolve: {
      seed: function() { return null; }
    }
  })
  .state('community.editSeed', {
    url: '/s/:seedId/edit',
    views: {
      community: {
        templateUrl: '/ui/seeds/edit.tpl.html',
        controller: 'SeedEditCtrl'
      }
    },
    resolve: {
      seed: ['Seed', '$stateParams', function(Seed, $stateParams) {
        return Seed.get({id: $stateParams.seedId}).$promise;
      }]
    }

  })
  .state('seed', {
    url: '/s/:seedId',
    parent: 'community',
    views: {
      "community": {
        templateUrl: '/ui/seeds/show.tpl.html',
        controller: 'SeedCtrl'
      }
    },
    resolve: {
      seed: ['Seed', '$stateParams', function(Seed, $stateParams) {
        return Seed.get({id: $stateParams.seedId}).$promise;
      }]
    },
    data: {
      singlePost: true
    }
  })
  .state('community.search', {
    url: "/search?q",
    resolve: {
      query: ['$stateParams', function($stateParams) {
        return $stateParams.q;
      }]
    },
    views: {
      "community": {
        templateUrl: '/ui/app/search.tpl.html',
        controller: 'SearchCtrl'
      }
    }
  });

};

var profileStates = function(stateProvider) {
  stateProvider
  .state('profile', {
    parent: 'main',
    url: '/u/:id',
    resolve: {
      editable: ['currentUser', '$stateParams', function(currentUser, $stateParams) {
        return parseInt(currentUser.id) == parseInt($stateParams.id);
      }],
      user: ['User', 'editable', '$stateParams', 'currentUser', function(User, editable, $stateParams, currentUser) {
        if (editable) {
          return currentUser;
        } else {
          return User.get({id: $stateParams.id}).$promise;
        }
      }],
      posts: ['user', function(user) {
        return user.seeds().$promise;
      }]
    },
    views: {
      'main': {
        templateUrl: '/ui/profile/base.tpl.html',
        controller: 'ProfileCtrl'
      }
    }
  })
  .state('profile.contributions', {
    url: '/contributions',
    resolve: {
      contributions: ['user', function(user) {
        return user.contributions().$promise;
      }]
    },
    views: {
      'tab': {
        templateUrl: '/ui/profile/contributions.tpl.html',
        controller: 'ProfileContributionsCtrl'
      }
    }
  })
  .state('profile.thanks', {
    url: '/thanks',
    resolve: {
      thanks: ['user', function(user) {
        return user.thanks().$promise;
      }]
    },
    views: {
      'tab': {
        templateUrl: '/ui/profile/thanks.tpl.html',
        controller: 'ProfileThanksCtrl'
      }
    }
  })
  .state('editProfile', {
    url: '/edit-profile',
    parent: 'main',
    views: {
      'main': {
        templateUrl: '/ui/profile/edit.tpl.html',
        controller: 'ProfileEditCtrl'
      }
    }
  });
}

var dependencies = ['$stateProvider', '$urlRouterProvider'];
dependencies.push(function($stateProvider, $urlRouterProvider) {

  $urlRouterProvider.otherwise(function($injector, $location){
    var state = $injector.get('$state');
    Rollbar.warning("404 Error: " + $location.path());
    state.go('404');
    return $location.path();
  });

  $urlRouterProvider.when('/c/:community/s/:seedId/comments', '/c/:community/s/:seedId');

  $stateProvider
    .state("404", {
      templateUrl: '/ui/app/404.tpl.html'
    })
    .state('main', {
      abstract: true,
      template: "

",
      resolve: {
        // Use the resource to fetch data from the server
        oldCurrentUser: ['CurrentUser', function(CurrentUser) {
          return CurrentUser.get().$promise;
        }],
        // user info has to be fetched from the new API for editing on
        // the new profile. eventually the old CurrentUser will be replaced
        // with this one.
        currentUser: ['User', function(User) {
          return User.current().$promise;
        }]
      },
      controller: ['$rootScope', 'oldCurrentUser', '$stateParams', function($rootScope, oldCurrentUser, $stateParams) {
        $rootScope.currentUser = oldCurrentUser;
      }]
    })
    .state('home', {
      url: '/',
      // redirects to hylo-play which resolves the URL to a community.
      onEnter: function reloadCtrl() {
        window.location.replace('/');
      }
    })
    .state('userSettings', {
      url: '/settings',
      parent: 'main',
      views: {
        "main": {
          templateUrl: '/ui/user/settings.tpl.html',
          controller: 'UserSettingsCtrl'
        }
      }
    })
    .state('network', {
      url: "/n/:network",
      views: {
        "": {
          templateUrl: '/ui/app/network.tpl.html',
          controller: 'NetworkCtrl'
        }
      }
    });

    communityStates($stateProvider);
    profileStates($stateProvider);

});

module.exports = dependencies;<|endoftext|>


<commit_before>/* global describe, it */

var requirejs = require("requirejs");
var assert = require("assert");
var should = require("should");
var skill = require('fs').readFileSync('./app/data/web.json', 'utf8');
var jsdom = require('mocha-jsdom');

requirejs.config({
  baseUrl: 'app/',
  nodeRequire: require
});

describe('Talent Tree Test', function () {
  var TalentTree;
  jsdom();

  before(function (done) {
    requirejs(['scripts/TalentTree'], function (TalentTree_Class) {
      TalentTree = TalentTree_Class;
      done();
    });
  });
  var all_skills = JSON.parse(skill);

  describe('Parse Test', function () {
    it('should parse html talent tree', function () {
      var talent = new TalentTree(all_skills);
      talent.avatarName().should.equal('Name');
      talent.noAvatarName().should.equal(true);
      talent.canShare().should.equal(false);
    });

    it('should remove avatar tips after set name', function () {
      var talent = new TalentTree(all_skills);
      talent.avatarName('Phodal');
      talent.avatarName().should.equal('Phodal');
      talent.noAvatarName().should.equal(false);
      talent.canShare().should.equal(true);
    });
  });

  describe('Level Test', function () {
    describe('Hash Test', function () {
      it('should parse hash to talent tree', function () {
        var talent = new TalentTree(all_skills);
        talent.updateHash("_m2n2x2y2_1_Phodal");
        talent.level().should.equal(9);
        talent.avatarName().should.equal("Phodal");
      });
    });
  });
});
<commit_msg>Add more test for parse hash<commit_after>/* global describe, it */

var requirejs = require("requirejs");
var assert = require("assert");
var should = require("should");
var skill = require('fs').readFileSync('./app/data/web.json', 'utf8');
var jsdom = require('mocha-jsdom');

requirejs.config({
  baseUrl: 'app/',
  nodeRequire: require
});

describe('Talent Tree Test', function () {
  var TalentTree;
  jsdom();

  before(function (done) {
    requirejs(['scripts/TalentTree'], function (TalentTree_Class) {
      TalentTree = TalentTree_Class;
      done();
    });
  });
  var all_skills = JSON.parse(skill);

  describe('Parse Test', function () {
    it('should parse html talent tree', function () {
      var talent = new TalentTree(all_skills);
      talent.avatarName().should.equal('Name');
      talent.noAvatarName().should.equal(true);
      talent.canShare().should.equal(false);
    });

    it('should remove avatar tips after set name', function () {
      var talent = new TalentTree(all_skills);
      talent.avatarName('Phodal');
      talent.avatarName().should.equal('Phodal');
      talent.noAvatarName().should.equal(false);
      talent.canShare().should.equal(true);
    });
  });

  describe('Level Test', function () {
    describe('Hash Test', function () {
      it('should parse hash to talent tree', function () {
        var talent = new TalentTree(all_skills);
        talent.updateHash("_m2n2x2y2_1_Phodal");
        talent.level().should.equal(9);
        talent.avatarName().should.equal("Phodal");
      });

      it('should parse hash to talent tree with portrait', function () {
        var talent = new TalentTree(all_skills);
        talent.updateHash("_a2b2c2de3fgh3i2jklm2n2opqr2s2tuvwx2y2z_2_Name");
        talent.level().should.equal(41);
        talent.portrait().should.equal(2);
        talent.avatarName().should.equal("Name");
      });
    });
  });
});
<|endoftext|>


<commit_before>var _ = require('underscore')
  , config = require('../config')
  , everypaas = require('everypaas')
  , nodemailer = require("nodemailer")
  , moment = require('moment')
  , logging = require('./logging')
  , jade = require('jade')
  , fs = require('fs')
  , path = require('path')
  , User = require('./models').User
  ;

/*
 * loading all of the email templates at server start
 */

function renderJade(filepath) {
  return jade.compile(fs.readFileSync(filepath, 'utf8'),{filename: filepath });
}

function renderUnderscore(filepath) {
  return _.template(fs.readFileSync(filepath, 'utf8'));
}

var beta_invite_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','beta_invite.jade'));

var beta_invite_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','beta_invite.jade'));

var notify_password_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_password_change.jade'));

var notify_password_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_password_change.jade'));

var notify_email_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_email_change.jade'));

var notify_email_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_email_change.jade'));

var test_fail_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_fail.jade'));

var test_succeed_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_succeed.jade'));

var test_succeed_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_succeed.html'));

var test_fail_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_fail.html'));

var collaborator_invite_new_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_new_user.jade'));

var collaborator_invite_new_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_new_user.jade'));

var collaborator_invite_existing_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_existing_user.jade'));

var collaborator_invite_existing_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_existing_user.jade'));

/*
 * opening smtp connection
 */
var smtpTransport;

// Try using SendGrid / Mailgun
if (everypaas.getSMTP() !== null) {
  console.log("Using SMTP transport: %j", everypaas.getSMTP());
  smtpTransport = nodemailer.createTransport.apply(null, everypaas.getSMTP());
} else {
  if (config.sendgrid) {
    console.log("Using Sendgrid transport from config file");
    smtpTransport = nodemailer.createTransport("SMTP",{
      service: "SendGrid",
      auth: {
          user: config.sendgrid.username,
          pass: config.sendgrid.password
      }
    });
  } else if (config.smtp) {
    console.log("Using SMTP transport from config file");
    smtpTransport = nodemailer.createTransport("SMTP",{
      host:config.smtp.host,
      port:parseInt(config.smtp.port),
      auth: {
          user: config.smtp.username,
          pass: config.smtp.password
      }
    });
  }
}

var send = exports.send = function(to, subject, text_body, html_body, from, domain)
{
  var from = from || "Strider <notifications@striderapp.com>";
  var domain = domain || "striderapp.com";

  var mailOptions = {
    from: from, // sender address
    to: to, // list of receivers
    subject: subject, // Subject line
    text: text_body, // plaintext body_template
    html: html_body // html body
  }
  // send mail with defined transport object
  smtpTransport.sendMail(mailOptions, function(error, response){
      if(error){
          console.log("Error sending email:",error);
      }else{
          console.log("Email sent: " + response.message);
      }

    });

}

var elapsed_time = exports.elapsed_time = function(start,finish) {
  var in_seconds = (finish - start) / 1000;
  if (in_seconds>60)
  {
    return (Math.floor(in_seconds / 60) + "m " + Math.round(in_seconds % 60) + "s");
  } else {
    return (Math.round(in_seconds) + "s");
  }
}

exports.send_test_ok = function(job, repo_config)
{
    var display_name = repo_config.url.replace(/^.*com\//gi, '');
    var subject = "[STRIDER] - " + display_name + " test success - " + job.id.substr(0,8);
    var duration = elapsed_time(job.created_timestamp.getTime(),job.finished_timestamp.getTime());
    var url = config.strider_server_name + "/" + display_name + "/job/" + job.id;
    var to = job._owner.email;

    var body_html;

    body_html = test_succeed_html(
      { display_name:display_name,
        finish_time:moment(job.finished_timestamp).format('YYYY-MM-DD h:mm a'),
        elapsed_time:duration,
        url:url,
        subject:subject,
        log_tail:format_stdmerged(job.stdmerged, "html")
      }
    );

    var body_text = test_succeed_plaintext(
      { display_name:display_name,
        finish_time:job.finished_timestamp,
        elapsed_time:duration,
        url:url,
        log_tail:format_stdmerged(job.stdmerged, "plaintext")
      }
    );
    Step(
      function() {
        // Notify owner.
        send(to, subject, body_text, body_html);
        // Look up email addresses for each collaborator
        var group = this.group();
        _.each(repo_config.collaborators, function(c) {
          User.findOne({_id:c.user_id}, group());
        });
      },
      function (err, users) {
        if (err) throw err;
        // Mail each collaborator
        _.each(users, function(u) {
            send(u.email, subject, body_text, body_html);
        });

      }
    );
}

exports.send_test_fail = function(job, repo_config)
{
    var display_name = repo_config.url.replace(/^.*com\//gi, '');
    var subject = "[STRIDER] - " + display_name + " test failure - " + job.id.substr(0,8);

    var duration = elapsed_time(job.created_timestamp.getTime(),job.finished_timestamp.getTime());
    var url = config.strider_server_name + "/" + display_name + "/job/" + job.id;
    console.debug("test url for email: " + url);

    var to = job._owner.email;

    var body_html;

    body_html = test_fail_html(
      { display_name:display_name,
        finish_time:moment(job.finished_timestamp).format('YYYY-MM-DD h:mm a'),
        elapsed_time:duration,
        url:url,
        subject:subject,
        log_tail:format_stdmerged(job.stdmerged, "html")
      }
    );

    var body_text = test_fail_plaintext(
      { display_name:display_name,
        finish_time:job.finished_timestamp,
        elapsed_time:duration,
        url:url,
        log_tail:format_stdmerged(job.stdmerged, "plaintext")
      }
    );
    Step(
      function() {
        // Notify owner.
        send(to, subject, body_text, body_html);
        // Look up email addresses for each collaborator
        var group = this.group();
        _.each(repo_config.collaborators, function(c) {
          User.findOne({_id:c.user_id}, group());
        });
      },
      function (err, users) {
        if (err) throw err;
        // Mail each collaborator
        _.each(users, function(u) {
            send(u.email, subject, body_text, body_html);
        });

      }
    );
}

/*
 * format_stdmerged()
 *
 * Format the stdmerged property (test std stream output) for sendgrid consumption.
 * <stdmerged> - Job's stdmerged property.
 */
var format_stdmerged = exports.format_stdmerged = function (stdmerged,email_format)
{
  // 4k
  var start = stdmerged.length - 1 - 4096;
  if (start < 0) {
    start = 0;
  }
  var tlog = stdmerged.slice(start, stdmerged.length - 1).replace(/^\s+|\s+$/g,"");
  // Start each line with a space

  tlines = tlog.split('\n');

  b = new Buffer(8192);
  var offset = 0;
  _.each(tlines, function(l) {
    if (email_format === 'plaintext') {
      var towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "\n";
    } else {
      var towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "<br>\n";
    }

    b.write(towrite, offset, towrite.length);
    offset += towrite.length;
  });

  return b.toString('utf8', 0, offset);
}

exports.send_invite = function(code, email)
{
    var subject = "Strider Beta Invitation";

    var body_html = beta_invite_html({ code:code, strider_server_name:config.strider_server_name });
    var body_text = beta_invite_plaintext({ code:code, strider_server_name:config.strider_server_name });
    var to = email;

    send(to, subject, body_text, body_html);
}

exports.notify_password_change = function (user) {
  var current_time = new Date();
  var subject = "[STRIDER] - Password Change Notification - "
              + current_time.getHours()
              + ":" + current_time.getMinutes();
  var body_text = notify_password_change_plaintext();
  var body_html = notify_password_change_html();
  var to = user.email;

  send(to, subject, body_text,body_html);
}

exports.notify_email_change = function (user, old_email) {

  var current_time = new Date();
  var subject = "[STRIDER] - Email Address Change Notification - " + current_time.getHours() + ":" + current_time.getMinutes() ;

  var to = user.email;

  var body_text = notify_email_change_plaintext({old_email: old_email, user: user});
  var body_html = notify_email_change_html({old_email: old_email, user: user});

  send(to, subject, body_text,body_html);
  send(old_email,subject,body_text,body_html);

  console.log("send email change notification to " + old_email + " and " + to);
}

exports.send_invite_collaborator_new_user = function(req, email, code, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  jade_variables =
    {
      inviter: req.user.email,
      display_name: display_name,
      code: code,
      strider_server_name: config.strider_server_name
    };

  var body_text = collaborator_invite_new_plaintext(jade_variables);
  var body_html = collaborator_invite_new_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to new user " + email + " for " +  display_name);
}

exports.send_invite_collaborator_existing_user = function(req, email, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  jade_variables =
    {
      inviter: req.user.email,
      display_name: display_name,
      strider_server_name: config.strider_server_name
    };

  var body_text = collaborator_invite_existing_plaintext(jade_variables);
  var body_html = collaborator_invite_existing_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to existing user " + email + " for " +  display_name);
}

exports.notify_new_admin = function (user) {

  var current_time = new Date();
  var subject = "[STRIDER] - New Admin  - " + user + " - " + current_time.getHours() + ":" + current_time.getMinutes() ;
  var body_template = "Hello Core,\n\n" +
    "You have a new admin colleague: " + user + "\n\n" +
    "Hopefully this isn't unexpected.\n\n" +
    "Regards,\n" +
    " - StriderApp.com\n" +
    "   Brilliant Continuous Delivery";

    var body = body_template;

    var to = "core@beyondfog.com";
    send(to, subject, body,body);
    console.log("Sending admin notification for new admin " + user);
}
<commit_msg>update email "from" details<commit_after>var _ = require('underscore')
  , config = require('../config')
  , everypaas = require('everypaas')
  , nodemailer = require("nodemailer")
  , moment = require('moment')
  , logging = require('./logging')
  , jade = require('jade')
  , fs = require('fs')
  , path = require('path')
  , User = require('./models').User
  ;

/*
 * loading all of the email templates at server start
 */

function renderJade(filepath) {
  return jade.compile(fs.readFileSync(filepath, 'utf8'),{filename: filepath });
}

function renderUnderscore(filepath) {
  return _.template(fs.readFileSync(filepath, 'utf8'));
}

var beta_invite_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','beta_invite.jade'));

var beta_invite_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','beta_invite.jade'));

var notify_password_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_password_change.jade'));

var notify_password_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_password_change.jade'));

var notify_email_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_email_change.jade'));

var notify_email_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_email_change.jade'));

var test_fail_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_fail.jade'));

var test_succeed_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_succeed.jade'));

var test_succeed_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_succeed.html'));

var test_fail_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_fail.html'));

var collaborator_invite_new_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_new_user.jade'));

var collaborator_invite_new_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_new_user.jade'));

var collaborator_invite_existing_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_existing_user.jade'));

var collaborator_invite_existing_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_existing_user.jade'));

/*
 * opening smtp connection
 */
var smtpTransport;

// Try using SendGrid / Mailgun
if (everypaas.getSMTP() !== null) {
  console.log("Using SMTP transport: %j", everypaas.getSMTP());
  smtpTransport = nodemailer.createTransport.apply(null, everypaas.getSMTP());
} else {
  if (config.sendgrid) {
    console.log("Using Sendgrid transport from config file");
    smtpTransport = nodemailer.createTransport("SMTP",{
      service: "SendGrid",
      auth: {
          user: config.sendgrid.username,
          pass: config.sendgrid.password
      }
    });
  } else if (config.smtp) {
    console.log("Using SMTP transport from config file");
    smtpTransport = nodemailer.createTransport("SMTP",{
      host:config.smtp.host,
      port:parseInt(config.smtp.port),
      auth: {
          user: config.smtp.username,
          pass: config.smtp.password
      }
    });
  }
}

var send = exports.send = function(to, subject, text_body, html_body, from, domain)
{
  var from = from || "Strider <noreply@stridercd.com>";
  var domain = domain || "stridercd.com";

  var mailOptions = {
    from: from, // sender address
    to: to, // list of receivers
    subject: subject, // Subject line
    text: text_body, // plaintext body_template
    html: html_body // html body
  }
  // send mail with defined transport object
  smtpTransport.sendMail(mailOptions, function(error, response){
      if(error){
          console.log("Error sending email:",error);
      }else{
          console.log("Email sent: " + response.message);
      }

    });

}

var elapsed_time = exports.elapsed_time = function(start,finish) {
  var in_seconds = (finish - start) / 1000;
  if (in_seconds>60)
  {
    return (Math.floor(in_seconds / 60) + "m " + Math.round(in_seconds % 60) + "s");
  } else {
    return (Math.round(in_seconds) + "s");
  }
}

exports.send_test_ok = function(job, repo_config)
{
    var display_name = repo_config.url.replace(/^.*com\//gi, '');
    var subject = "[STRIDER] - " + display_name + " test success - " + job.id.substr(0,8);
    var duration = elapsed_time(job.created_timestamp.getTime(),job.finished_timestamp.getTime());
    var url = config.strider_server_name + "/" + display_name + "/job/" + job.id;
    var to = job._owner.email;

    var body_html;

    body_html = test_succeed_html(
      { display_name:display_name,
        finish_time:moment(job.finished_timestamp).format('YYYY-MM-DD h:mm a'),
        elapsed_time:duration,
        url:url,
        subject:subject,
        log_tail:format_stdmerged(job.stdmerged, "html")
      }
    );

    var body_text = test_succeed_plaintext(
      { display_name:display_name,
        finish_time:job.finished_timestamp,
        elapsed_time:duration,
        url:url,
        log_tail:format_stdmerged(job.stdmerged, "plaintext")
      }
    );
    Step(
      function() {
        // Notify owner.
        send(to, subject, body_text, body_html);
        // Look up email addresses for each collaborator
        var group = this.group();
        _.each(repo_config.collaborators, function(c) {
          User.findOne({_id:c.user_id}, group());
        });
      },
      function (err, users) {
        if (err) throw err;
        // Mail each collaborator
        _.each(users, function(u) {
            send(u.email, subject, body_text, body_html);
        });

      }
    );
}

exports.send_test_fail = function(job, repo_config)
{
    var display_name = repo_config.url.replace(/^.*com\//gi, '');
    var subject = "[STRIDER] - " + display_name + " test failure - " + job.id.substr(0,8);

    var duration = elapsed_time(job.created_timestamp.getTime(),job.finished_timestamp.getTime());
    var url = config.strider_server_name + "/" + display_name + "/job/" + job.id;
    console.debug("test url for email: " + url);

    var to = job._owner.email;

    var body_html;

    body_html = test_fail_html(
      { display_name:display_name,
        finish_time:moment(job.finished_timestamp).format('YYYY-MM-DD h:mm a'),
        elapsed_time:duration,
        url:url,
        subject:subject,
        log_tail:format_stdmerged(job.stdmerged, "html")
      }
    );

    var body_text = test_fail_plaintext(
      { display_name:display_name,
        finish_time:job.finished_timestamp,
        elapsed_time:duration,
        url:url,
        log_tail:format_stdmerged(job.stdmerged, "plaintext")
      }
    );
    Step(
      function() {
        // Notify owner.
        send(to, subject, body_text, body_html);
        // Look up email addresses for each collaborator
        var group = this.group();
        _.each(repo_config.collaborators, function(c) {
          User.findOne({_id:c.user_id}, group());
        });
      },
      function (err, users) {
        if (err) throw err;
        // Mail each collaborator
        _.each(users, function(u) {
            send(u.email, subject, body_text, body_html);
        });

      }
    );
}

/*
 * format_stdmerged()
 *
 * Format the stdmerged property (test std stream output) for sendgrid consumption.
 * <stdmerged> - Job's stdmerged property.
 */
var format_stdmerged = exports.format_stdmerged = function (stdmerged,email_format)
{
  // 4k
  var start = stdmerged.length - 1 - 4096;
  if (start < 0) {
    start = 0;
  }
  var tlog = stdmerged.slice(start, stdmerged.length - 1).replace(/^\s+|\s+$/g,"");
  // Start each line with a space

  tlines = tlog.split('\n');

  b = new Buffer(8192);
  var offset = 0;
  _.each(tlines, function(l) {
    if (email_format === 'plaintext') {
      var towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "\n";
    } else {
      var towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "<br>\n";
    }

    b.write(towrite, offset, towrite.length);
    offset += towrite.length;
  });

  return b.toString('utf8', 0, offset);
}

exports.send_invite = function(code, email)
{
    var subject = "Strider Beta Invitation";

    var body_html = beta_invite_html({ code:code, strider_server_name:config.strider_server_name });
    var body_text = beta_invite_plaintext({ code:code, strider_server_name:config.strider_server_name });
    var to = email;

    send(to, subject, body_text, body_html);
}

exports.notify_password_change = function (user) {
  var current_time = new Date();
  var subject = "[STRIDER] - Password Change Notification - "
              + current_time.getHours()
              + ":" + current_time.getMinutes();
  var body_text = notify_password_change_plaintext();
  var body_html = notify_password_change_html();
  var to = user.email;

  send(to, subject, body_text,body_html);
}

exports.notify_email_change = function (user, old_email) {

  var current_time = new Date();
  var subject = "[STRIDER] - Email Address Change Notification - " + current_time.getHours() + ":" + current_time.getMinutes() ;

  var to = user.email;

  var body_text = notify_email_change_plaintext({old_email: old_email, user: user});
  var body_html = notify_email_change_html({old_email: old_email, user: user});

  send(to, subject, body_text,body_html);
  send(old_email,subject,body_text,body_html);

  console.log("send email change notification to " + old_email + " and " + to);
}

exports.send_invite_collaborator_new_user = function(req, email, code, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  jade_variables =
    {
      inviter: req.user.email,
      display_name: display_name,
      code: code,
      strider_server_name: config.strider_server_name
    };

  var body_text = collaborator_invite_new_plaintext(jade_variables);
  var body_html = collaborator_invite_new_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to new user " + email + " for " +  display_name);
}

exports.send_invite_collaborator_existing_user = function(req, email, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  jade_variables =
    {
      inviter: req.user.email,
      display_name: display_name,
      strider_server_name: config.strider_server_name
    };

  var body_text = collaborator_invite_existing_plaintext(jade_variables);
  var body_html = collaborator_invite_existing_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to existing user " + email + " for " +  display_name);
}

exports.notify_new_admin = function (user) {

  var current_time = new Date();
  var subject = "[STRIDER] - New Admin  - " + user + " - " + current_time.getHours() + ":" + current_time.getMinutes() ;
  var body_template = "Hello Core,\n\n" +
    "You have a new admin colleague: " + user + "\n\n" +
    "Hopefully this isn't unexpected.\n\n" +
    "Regards,\n" +
    " - StriderApp.com\n" +
    "   Brilliant Continuous Delivery";

    var body = body_template;

    var to = "core@beyondfog.com";
    send(to, subject, body,body);
    console.log("Sending admin notification for new admin " + user);
}
<|endoftext|>


<commit_before>const moment = require('moment');

const getLDAPTimestamp = (date) => `${moment(date).format('YYYYMMDDHHmmss')}Z`;

const getModifiedFilter = (timestamp, attributeName = 'modifyTimestamp') =>
	`(|(!(${attributeName}=*))(${attributeName}>=${timestamp}))`;

const filterForModifiedEntities = (lastChange, existingFilter = '') => {
	if (!/\d{14}Z/.test(lastChange)) {
		return existingFilter;
	}
	const filter = getModifiedFilter(lastChange);
	return `(&${existingFilter}${filter})`;
};

module.exports = {
	filterForModifiedEntities,
	getModifiedFilter,
	getLDAPTimestamp,
};
<commit_msg>Document deltaSyncUtils and remove unused method & dependency<commit_after>/**
 * Returns an LDAP filter to retrieve only recently updated entities (e.g. based on a
 * timestamp of a previous previous full sync). If the given attribute name does not
 * exist on the server, the resulting query gracefully falls back to all entities. The
 * logic is basically "search for all entities that either have a timestamp greater than
 * the given timestamp in the attribute with the given name, or don't have the attribute
 * at all".
 * @param {String} timestamp time of last modification, formatted as "YYYYMMDDHHmmssZ"
 * @param {String} attributeName optional attribute name to check
 */
const getModifiedFilter = (timestamp, attributeName = 'modifyTimestamp') =>
	`(|(!(${attributeName}=*))(${attributeName}>=${timestamp}))`;

/**
 * Returns an LDAP filter based on a given filter and a last change time. If lastChange is
 * undefined or does not match the correct format, the base filter is returned. Otherwise,
 * the base filter is combined with a filter for recently updated entities (i.e. updated
 * since lastChange).
 * @param {String} lastChange time of last modification, formatted as "YYYYMMDDHHmmssZ"
 * @param {String} existingFilter optional LDAP-compatible filter condition (including
 * outer parens) [defaults to '']
 * @example
 * 	filterForModifiedEntities(); // => ''
 * 	filterForModifiedEntities('20201020000000Z', '(objectClass=person)');
 * 	// => '(&(objectClass=person)(|(!(modifyTimestamp=*))(modifyTimestamp>=20201020000000Z)))'
 */
const filterForModifiedEntities = (lastChange, existingFilter = '') => {
	if (!/\d{14}Z/.test(lastChange)) {
		return existingFilter;
	}
	const filter = getModifiedFilter(lastChange);
	return `(&${existingFilter}${filter})`;
};

module.exports = {
	filterForModifiedEntities,
	getModifiedFilter,
};
<|endoftext|>


<commit_before>/*
* jQuery Mobile Framework : events
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, window, undefined ) {

// add new event shortcuts
$.each( "touchstart touchmove touchend orientationchange throttledresize " +
				"tap taphold swipe swipeleft swiperight scrollstart scrollstop".split( " " ), function( i, name ) {

	$.fn[ name ] = function( fn ) {
		return fn ? this.bind( name, fn ) : this.trigger( name );
	};

	$.attrFn[ name ] = true;
});

var supportTouch = $.support.touch,
	scrollEvent = "touchmove scroll",
	touchStartEvent = supportTouch ? "touchstart" : "mousedown",
	touchStopEvent = supportTouch ? "touchend" : "mouseup",
	touchMoveEvent = supportTouch ? "touchmove" : "mousemove";

function triggerCustomEvent( obj, eventType, event ) {
	var originalType = event.type;
	event.type = eventType;
	$.event.handle.call( obj, event );
	event.type = originalType;
}

// also handles scrollstop
$.event.special.scrollstart = {

	enabled: true,

	setup: function() {

		var thisObject = this,
			$this = $( thisObject ),
			scrolling,
			timer;

		function trigger( event, state ) {
			scrolling = state;
			triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
		}

		// iPhone triggers scroll after a small delay; use touchmove instead
		$this.bind( scrollEvent, function( event ) {

			if ( !$.event.special.scrollstart.enabled ) {
				return;
			}

			if ( !scrolling ) {
				trigger( event, true );
			}

			clearTimeout( timer );
			timer = setTimeout(function() {
				trigger( event, false );
			}, 50 );
		});
	}
};

// also handles taphold
$.event.special.tap = {
	setup: function() {
		var thisObject = this,
			$this = $( thisObject );

		$this.bind( "vmousedown", function( event ) {

			if ( event.which && event.which !== 1 ) {
				return false;
			}

			var touching = true,
				origTarget = event.target,
				origEvent = event.originalEvent,
				timer;

			function clearTapHandlers() {
				touching = false;
				clearTimeout(timer);

				$this.unbind( "vclick", clickHandler )
					.unbind( "vmousecancel", clearTapHandlers );
			}

			function clickHandler(event) {
				clearTapHandlers();

				// ONLY trigger a 'tap' event if the start target is
				// the same as the stop target.
				if ( origTarget == event.target ) {
					triggerCustomEvent( thisObject, "tap", event );
				}
			}

			$this.bind( "vmousecancel", clearTapHandlers )
				.bind( "vclick", clickHandler );

			timer = setTimeout(function() {
				if ( touching ) {
					triggerCustomEvent( thisObject, "taphold", event );
				}
			}, 750 );
		});
	}
};

// also handles swipeleft, swiperight
$.event.special.swipe = {
	setup: function() {
		var thisObject = this,
			$this = $( thisObject );

		$this.bind( touchStartEvent, function( event ) {
			var data = event.originalEvent.touches ?
								event.originalEvent.touches[ 0 ] : event,
				start = {
					time: ( new Date() ).getTime(),
					coords: [ data.pageX, data.pageY ],
					origin: $( event.target )
				},
				stop;

			function moveHandler( event ) {

				if ( !start ) {
					return;
				}

				var data = event.originalEvent.touches ?
						event.originalEvent.touches[ 0 ] : event;

				stop = {
					time: ( new Date() ).getTime(),
					coords: [ data.pageX, data.pageY ]
				};

				// prevent scrolling
				if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > 10 ) {
					event.preventDefault();
				}
			}

			$this.bind( touchMoveEvent, moveHandler )
				.one( touchStopEvent, function( event ) {
					$this.unbind( touchMoveEvent, moveHandler );

					if ( start && stop ) {
						if ( stop.time - start.time < 1000 &&
								Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > 30 &&
								Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < 75 ) {

							start.origin.trigger( "swipe" )
								.trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" );
						}
					}
					start = stop = undefined;
				});
		});
	}
};

(function( $, window ) {
	// "Cowboy" Ben Alman

	var win = $( window ),
		special_event,
		get_orientation,
		last_orientation;

	$.event.special.orientationchange = special_event = {
		setup: function() {
			// If the event is supported natively, return false so that jQuery
			// will bind to the event using DOM methods.
			if ( $.support.orientation ) {
				return false;
			}

			// Get the current orientation to avoid initial double-triggering.
			last_orientation = get_orientation();

			// Because the orientationchange event doesn't exist, simulate the
			// event by testing window dimensions on resize.
			win.bind( "throttledresize", handler );
		},
		teardown: function(){
			// If the event is not supported natively, return false so that
			// jQuery will unbind the event using DOM methods.
			if ( $.support.orientation ) {
				return false;
			}

			// Because the orientationchange event doesn't exist, unbind the
			// resize event handler.
			win.unbind( "throttledresize", handler );
		},
		add: function( handleObj ) {
			// Save a reference to the bound event handler.
			var old_handler = handleObj.handler;

			handleObj.handler = function( event ) {
				// Modify event object, adding the .orientation property.
				event.orientation = get_orientation();

				// Call the originally-bound event handler and return its result.
				return old_handler.apply( this, arguments );
			};
		}
	};

	// If the event is not supported natively, this handler will be bound to
	// the window resize event to simulate the orientationchange event.
	function handler() {
		// Get the current orientation.
		var orientation = get_orientation();

		if ( orientation !== last_orientation ) {
			// The orientation has changed, so trigger the orientationchange event.
			last_orientation = orientation;
			win.trigger( "orientationchange" );
		}
	};

	// Get the current page orientation. This method is exposed publicly, should it
	// be needed, as jQuery.event.special.orientationchange.orientation()
	$.event.special.orientationchange.orientation = get_orientation = function() {
		var elem = document.documentElement;
		return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape";
	};

})( jQuery, window );

// throttled resize event
(function() {

	$.event.special.throttledresize = {
		setup: function() {
			$( this ).bind( "resize", handler );
		},
		teardown: function(){
			$( this ).unbind( "resize", handler );
		}
	};

	var throttle = 250,
		handler = function() {
			curr = ( new Date() ).getTime();
			diff = curr - lastCall;

			if ( diff >= throttle ) {

				lastCall = curr;
				$( this ).trigger( "throttledresize" );

			} else {

				if ( heldCall ) {
					clearTimeout( heldCall );
				}

				// Promise a held call will still execute
				heldCall = setTimeout( handler, throttle - diff );
			}
		},
		lastCall = 0,
		heldCall,
		curr,
		diff;
})();

$.each({
	scrollstop: "scrollstart",
	taphold: "tap",
	swipeleft: "swipe",
	swiperight: "swipe"
}, function( event, sourceEvent ) {

	$.event.special[ event ] = {
		setup: function() {
			$( this ).bind( sourceEvent, $.noop );
		}
	};
});

})( jQuery, this );
<commit_msg>jQuery core style guide conformance: jquery.mobile.event.js - fix string concat & split<commit_after>/*
* jQuery Mobile Framework : events
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, window, undefined ) {

// add new event shortcuts
$.each( ( "touchstart touchmove touchend orientationchange throttledresize " +
					"tap taphold swipe swipeleft swiperight scrollstart scrollstop" ).split( " " ), function( i, name ) {

	$.fn[ name ] = function( fn ) {
		return fn ? this.bind( name, fn ) : this.trigger( name );
	};

	$.attrFn[ name ] = true;
});

var supportTouch = $.support.touch,
	scrollEvent = "touchmove scroll",
	touchStartEvent = supportTouch ? "touchstart" : "mousedown",
	touchStopEvent = supportTouch ? "touchend" : "mouseup",
	touchMoveEvent = supportTouch ? "touchmove" : "mousemove";

function triggerCustomEvent( obj, eventType, event ) {
	var originalType = event.type;
	event.type = eventType;
	$.event.handle.call( obj, event );
	event.type = originalType;
}

// also handles scrollstop
$.event.special.scrollstart = {

	enabled: true,

	setup: function() {

		var thisObject = this,
			$this = $( thisObject ),
			scrolling,
			timer;

		function trigger( event, state ) {
			scrolling = state;
			triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
		}

		// iPhone triggers scroll after a small delay; use touchmove instead
		$this.bind( scrollEvent, function( event ) {

			if ( !$.event.special.scrollstart.enabled ) {
				return;
			}

			if ( !scrolling ) {
				trigger( event, true );
			}

			clearTimeout( timer );
			timer = setTimeout(function() {
				trigger( event, false );
			}, 50 );
		});
	}
};

// also handles taphold
$.event.special.tap = {
	setup: function() {
		var thisObject = this,
			$this = $( thisObject );

		$this.bind( "vmousedown", function( event ) {

			if ( event.which && event.which !== 1 ) {
				return false;
			}

			var touching = true,
				origTarget = event.target,
				origEvent = event.originalEvent,
				timer;

			function clearTapHandlers() {
				touching = false;
				clearTimeout(timer);

				$this.unbind( "vclick", clickHandler )
					.unbind( "vmousecancel", clearTapHandlers );
			}

			function clickHandler(event) {
				clearTapHandlers();

				// ONLY trigger a 'tap' event if the start target is
				// the same as the stop target.
				if ( origTarget == event.target ) {
					triggerCustomEvent( thisObject, "tap", event );
				}
			}

			$this.bind( "vmousecancel", clearTapHandlers )
				.bind( "vclick", clickHandler );

			timer = setTimeout(function() {
				if ( touching ) {
					triggerCustomEvent( thisObject, "taphold", event );
				}
			}, 750 );
		});
	}
};

// also handles swipeleft, swiperight
$.event.special.swipe = {
	setup: function() {
		var thisObject = this,
			$this = $( thisObject );

		$this.bind( touchStartEvent, function( event ) {
			var data = event.originalEvent.touches ?
								event.originalEvent.touches[ 0 ] : event,
				start = {
					time: ( new Date() ).getTime(),
					coords: [ data.pageX, data.pageY ],
					origin: $( event.target )
				},
				stop;

			function moveHandler( event ) {

				if ( !start ) {
					return;
				}

				var data = event.originalEvent.touches ?
						event.originalEvent.touches[ 0 ] : event;

				stop = {
					time: ( new Date() ).getTime(),
					coords: [ data.pageX, data.pageY ]
				};

				// prevent scrolling
				if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > 10 ) {
					event.preventDefault();
				}
			}

			$this.bind( touchMoveEvent, moveHandler )
				.one( touchStopEvent, function( event ) {
					$this.unbind( touchMoveEvent, moveHandler );

					if ( start && stop ) {
						if ( stop.time - start.time < 1000 &&
								Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > 30 &&
								Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < 75 ) {

							start.origin.trigger( "swipe" )
								.trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" );
						}
					}
					start = stop = undefined;
				});
		});
	}
};

(function( $, window ) {
	// "Cowboy" Ben Alman

	var win = $( window ),
		special_event,
		get_orientation,
		last_orientation;

	$.event.special.orientationchange = special_event = {
		setup: function() {
			// If the event is supported natively, return false so that jQuery
			// will bind to the event using DOM methods.
			if ( $.support.orientation ) {
				return false;
			}

			// Get the current orientation to avoid initial double-triggering.
			last_orientation = get_orientation();

			// Because the orientationchange event doesn't exist, simulate the
			// event by testing window dimensions on resize.
			win.bind( "throttledresize", handler );
		},
		teardown: function(){
			// If the event is not supported natively, return false so that
			// jQuery will unbind the event using DOM methods.
			if ( $.support.orientation ) {
				return false;
			}

			// Because the orientationchange event doesn't exist, unbind the
			// resize event handler.
			win.unbind( "throttledresize", handler );
		},
		add: function( handleObj ) {
			// Save a reference to the bound event handler.
			var old_handler = handleObj.handler;

			handleObj.handler = function( event ) {
				// Modify event object, adding the .orientation property.
				event.orientation = get_orientation();

				// Call the originally-bound event handler and return its result.
				return old_handler.apply( this, arguments );
			};
		}
	};

	// If the event is not supported natively, this handler will be bound to
	// the window resize event to simulate the orientationchange event.
	function handler() {
		// Get the current orientation.
		var orientation = get_orientation();

		if ( orientation !== last_orientation ) {
			// The orientation has changed, so trigger the orientationchange event.
			last_orientation = orientation;
			win.trigger( "orientationchange" );
		}
	};

	// Get the current page orientation. This method is exposed publicly, should it
	// be needed, as jQuery.event.special.orientationchange.orientation()
	$.event.special.orientationchange.orientation = get_orientation = function() {
		var elem = document.documentElement;
		return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape";
	};

})( jQuery, window );

// throttled resize event
(function() {

	$.event.special.throttledresize = {
		setup: function() {
			$( this ).bind( "resize", handler );
		},
		teardown: function(){
			$( this ).unbind( "resize", handler );
		}
	};

	var throttle = 250,
		handler = function() {
			curr = ( new Date() ).getTime();
			diff = curr - lastCall;

			if ( diff >= throttle ) {

				lastCall = curr;
				$( this ).trigger( "throttledresize" );

			} else {

				if ( heldCall ) {
					clearTimeout( heldCall );
				}

				// Promise a held call will still execute
				heldCall = setTimeout( handler, throttle - diff );
			}
		},
		lastCall = 0,
		heldCall,
		curr,
		diff;
})();

$.each({
	scrollstop: "scrollstart",
	taphold: "tap",
	swipeleft: "swipe",
	swiperight: "swipe"
}, function( event, sourceEvent ) {

	$.event.special[ event ] = {
		setup: function() {
			$( this ).bind( sourceEvent, $.noop );
		}
	};
});

})( jQuery, this );
<|endoftext|>


<commit_before>var gulp = require('gulp');
var git = require('gulp-git');
var prompt = require('gulp-prompt');

var commitMessage = '';

gulp.task('prompt', function(){

	return gulp.src('./*')
	.pipe(prompt.prompt({
		type: 'input',
		name: 'message',
		message: 'Write a commit message:'
		},
		function(res){
			commitMessage = res.message;
		}
	));
});

gulp.task('addAll', ['prompt'], function(){
	return gulp.src('./*')
	.pipe(git.add());
});

gulp.task('commit', ['addAll'], function(){
	return gulp.src('./*', {buffer: false})
	.pipe(git.commit(commitMessage, {emitData: true, maxBuffer: 'Infinity'}));
});

gulp.task('pushMaster', ['commit'], function(){
	git.push('origin', 'master', function(err){
		if (err) throw err;
	});
});

gulp.task('default', ['pushMaster']);<commit_msg>removed gulp options to test if necessary<commit_after>var gulp = require('gulp');
var git = require('gulp-git');
var prompt = require('gulp-prompt');

var commitMessage = '';

gulp.task('prompt', function(){

	return gulp.src('./*')
	.pipe(prompt.prompt({
		type: 'input',
		name: 'message',
		message: 'Write a commit message:'
		},
		function(res){
			commitMessage = res.message;
		}
	));
});

gulp.task('addAll', ['prompt'], function(){
	return gulp.src('./*')
	.pipe(git.add());
});

gulp.task('commit', ['addAll'], function(){
	return gulp.src('./*')
	.pipe(git.commit(commitMessage, {maxBuffer: 'Infinity'}));
});

gulp.task('pushMaster', ['commit'], function(){
	git.push('origin', 'master', function(err){
		if (err) throw err;
	});
});

gulp.task('default', ['pushMaster']);<|endoftext|>


<commit_before>import * as React from 'react';
import { Motion } from 'react-motion';
import { Modal } from 'react-native';
import spring from './spring';

class SharedElementMotion extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.reset = this.reset.bind(this);
        this.state = {
            url: this.getStateNavigator().stateContext.url,
            sharedElements: this.context.getSharedElements(),
            animatedElements: {},
            force: 1,
        };
    }
    static contextTypes = {
        stateNavigator: React.PropTypes.object,
        getSharedElements: React.PropTypes.func
    }
    getStateNavigator() {
        return this.props.stateNavigator || this.context.stateNavigator;
    }
    componentDidMount() {
        this.getStateNavigator().onNavigate(this.reset);
        this.animate();
    }
    componentWillUnmount() {
        this.getStateNavigator().offNavigate(this.reset);
        cancelAnimationFrame(this.animateId);
    }
    animate() {
        this.animatedId = requestAnimationFrame(() => {
            if (this.state.url !== this.getStateNavigator().stateContext.url)
                return;
            var {onAnimating} = this.props;
            var sharedElements = this.context.getSharedElements();
            for(var i = 0; i < sharedElements.length && onAnimating; i++) {
                var {name, oldElement: old, mountedElement: mounted} = sharedElements[i];
                if (!this.state.animatedElements[name])
                    onAnimating(name, old.ref, mounted.ref, old.data, mounted.data);
            }
            if (sharedElements.length !== 0)
                this.setState({sharedElements});
            this.animate();
        });
    }
    reset() {
        if (this.state.url !== this.getStateNavigator().stateContext.url)
            this.setState(({force}) => ({sharedElements: [], animatedElements: {}, force: force + 1}));
        else
            this.animate();
    }
    stripStyle(style) {
        var newStyle = {};
        for(var key in style) {
            newStyle[key] = style[key].val;
        }
        return newStyle;
    }
    onAnimated(name, old, mounted) {
        this.setState(
            ({animatedElements}) => ({animatedElements: {...animatedElements, [name]: true}}),
            () => {
                var {onAnimated = () => {}} = this.props;
                var unmounted = old.url = this.getStateNavigator().stateContext.oldUrl;
                onAnimated(name, !unmounted ? old.ref : null, mounted.ref, !unmounted ? old.data : null, mounted.data)
            }
        );
    }
    render() {
        var {children, elementStyle} = this.props;
        var {url, sharedElements, animatedElements, force} = this.state;
        return (url === this.getStateNavigator().stateContext.url &&
            <Modal
                transparent={true} animationType="none" onRequestClose={() => {}}
                visible={sharedElements.length > Object.keys(animatedElements).length}
                supportedOrientations={['portrait', 'landscape', 'landscape-left', 'landscape-right']}>
                {sharedElements.map(({name, oldElement: old, mountedElement: mounted}) => (
                    <Motion
                        key={name} onRest={() => {this.onAnimated(name, old, mounted)}}
                        defaultStyle={{...this.stripStyle(elementStyle(name, {...old.measurements, ...old.data})), __force: 0}}
                        style={{...elementStyle(name, {...mounted.measurements, ...mounted.data}), __force: spring(force)}}>
                        {({__force, ...tweenStyle}) => children(tweenStyle, name, old.data, mounted.data)}
                    </Motion>
                ))}
            </Modal>
        );
    }
}

export default SharedElementMotion;
<commit_msg>Moved onAnimating into setState callback<commit_after>import * as React from 'react';
import { Motion } from 'react-motion';
import { Modal } from 'react-native';
import spring from './spring';

class SharedElementMotion extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.reset = this.reset.bind(this);
        this.state = {
            url: this.getStateNavigator().stateContext.url,
            sharedElements: this.context.getSharedElements(),
            animatedElements: {},
            force: 1,
        };
    }
    static contextTypes = {
        stateNavigator: React.PropTypes.object,
        getSharedElements: React.PropTypes.func
    }
    getStateNavigator() {
        return this.props.stateNavigator || this.context.stateNavigator;
    }
    componentDidMount() {
        this.getStateNavigator().onNavigate(this.reset);
        this.animate();
    }
    componentWillUnmount() {
        this.getStateNavigator().offNavigate(this.reset);
        cancelAnimationFrame(this.animateId);
    }
    animate() {
        this.animatedId = requestAnimationFrame(() => {
            if (this.state.url !== this.getStateNavigator().stateContext.url)
                return;
            var sharedElements = this.context.getSharedElements();
            if (sharedElements.length !== 0) {
                this.setState({sharedElements}, () => {
                    var {onAnimating} = this.props;
                    for(var i = 0; i < sharedElements.length && onAnimating; i++) {
                        var {name, oldElement: old, mountedElement: mounted} = sharedElements[i];
                        if (!this.state.animatedElements[name])
                            onAnimating(name, old.ref, mounted.ref, old.data, mounted.data);
                    }
                });
            }
            this.animate();
        });
    }
    reset() {
        if (this.state.url !== this.getStateNavigator().stateContext.url)
            this.setState(({force}) => ({sharedElements: [], animatedElements: {}, force: force + 1}));
        else
            this.animate();
    }
    stripStyle(style) {
        var newStyle = {};
        for(var key in style) {
            newStyle[key] = style[key].val;
        }
        return newStyle;
    }
    onAnimated(name, old, mounted) {
        this.setState(
            ({animatedElements}) => ({animatedElements: {...animatedElements, [name]: true}}),
            () => {
                var {onAnimated = () => {}} = this.props;
                var unmounted = old.url = this.getStateNavigator().stateContext.oldUrl;
                onAnimated(name, !unmounted ? old.ref : null, mounted.ref, !unmounted ? old.data : null, mounted.data)
            }
        );
    }
    render() {
        var {children, elementStyle} = this.props;
        var {url, sharedElements, animatedElements, force} = this.state;
        return (url === this.getStateNavigator().stateContext.url &&
            <Modal
                transparent={true} animationType="none" onRequestClose={() => {}}
                visible={sharedElements.length > Object.keys(animatedElements).length}
                supportedOrientations={['portrait', 'landscape', 'landscape-left', 'landscape-right']}>
                {sharedElements.map(({name, oldElement: old, mountedElement: mounted}) => (
                    <Motion
                        key={name} onRest={() => {this.onAnimated(name, old, mounted)}}
                        defaultStyle={{...this.stripStyle(elementStyle(name, {...old.measurements, ...old.data})), __force: 0}}
                        style={{...elementStyle(name, {...mounted.measurements, ...mounted.data}), __force: spring(force)}}>
                        {({__force, ...tweenStyle}) => children(tweenStyle, name, old.data, mounted.data)}
                    </Motion>
                ))}
            </Modal>
        );
    }
}

export default SharedElementMotion;
<|endoftext|>