<commit_before>import _ from 'lodash'
import { dbAdapter } from '../models'
import { extractMentions, extractMentionsWithIndices } from './mentions'

export const EVENT_TYPES = {
  MENTION_IN_POST:               'mention_in_post',
  MENTION_IN_COMMENT:            'mention_in_comment',
  MENTION_COMMENT_TO:            'mention_comment_to',
  USER_BANNED:                   'banned_user',
  USER_UNBANNED:                 'unbanned_user',
  BANNED_BY:                     'banned_by_user',
  UNBANNED_BY:                   'unbanned_by_user',
  USER_SUBSCRIBED:               'user_subscribed',
  USER_UNSUBSCRIBED:             'user_unsubscribed',
  SUBSCRIPTION_REQUESTED:        'subscription_requested',
  SUBSCRIPTION_REQUEST_REVOKED:  'subscription_request_revoked',
  SUBSCRIPTION_REQUEST_APPROVED: 'subscription_request_approved',
  SUBSCRIPTION_REQUEST_REJECTED: 'subscription_request_rejected',
  GROUP_CREATED:                 'group_created',
  GROUP_SUBSCRIBED:              'group_subscribed',
  GROUP_UNSUBSCRIBED:            'group_unsubscribed',
  GROUP_SUBSCRIPTION_REQUEST:    'group_subscription_requested',
  GROUP_REQUEST_REVOKED:         'group_subscription_request_revoked',
  GROUP_SUBSCRIPTION_APPROVED:   'group_subscription_approved',
  GROUP_SUBSCRIPTION_REJECTED:   'group_subscription_rejected',
  GROUP_ADMIN_PROMOTED:          'group_admin_promoted',
  GROUP_ADMIN_DEMOTED:           'group_admin_demoted',
  DIRECT_CREATED:                'direct',
  DIRECT_COMMENT_CREATED:        'direct_comment',

  MANAGED_GROUP_SUBSCRIPTION_APPROVED: 'managed_group_subscription_approved',
  MANAGED_GROUP_SUBSCRIPTION_REJECTED: 'managed_group_subscription_rejected',
};

export class EventService {
  static async onUserBanned(initiatorIntId, bannedUserIntId, wasSubscribed = false, hasRequestedSubscription = false) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_BANNED, initiatorIntId, bannedUserIntId);
    await dbAdapter.createEvent(bannedUserIntId, EVENT_TYPES.BANNED_BY, initiatorIntId, bannedUserIntId);
    if (wasSubscribed) {
      await this.onUserUnsubscribed(bannedUserIntId, initiatorIntId);
    }
    if (hasRequestedSubscription) {
      await this.onSubscriptionRequestRejected(bannedUserIntId, initiatorIntId);
    }
  }

  static async onUserUnbanned(initiatorIntId, unbannedUserIntId) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_UNBANNED, initiatorIntId, unbannedUserIntId);
    await dbAdapter.createEvent(unbannedUserIntId, EVENT_TYPES.UNBANNED_BY, initiatorIntId, unbannedUserIntId);
  }

  static async onUserSubscribed(initiatorIntId, subscribedUserIntId) {
    await dbAdapter.createEvent(subscribedUserIntId, EVENT_TYPES.USER_SUBSCRIBED, initiatorIntId, subscribedUserIntId);
  }

  static async onUserUnsubscribed(initiatorIntId, unsubscribedUserIntId) {
    await dbAdapter.createEvent(unsubscribedUserIntId, EVENT_TYPES.USER_UNSUBSCRIBED, initiatorIntId, unsubscribedUserIntId);
  }

  static async onSubscriptionRequestCreated(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUESTED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRevoked(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REVOKED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestApproved(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED, toUserIntId, fromUserIntId);
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.USER_SUBSCRIBED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRejected(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REJECTED, toUserIntId, fromUserIntId);
  }

  static async onGroupCreated(ownerIntId, groupIntId) {
    await dbAdapter.createEvent(ownerIntId, EVENT_TYPES.GROUP_CREATED, ownerIntId, null, groupIntId);
  }

  static async onGroupSubscribed(initiatorIntId, subscribedGroup) {
    await this._notifyGroupAdmins(subscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIBED, initiatorIntId, null, subscribedGroup.intId);
    });
  }

  static async onGroupUnsubscribed(initiatorIntId, unsubscribedGroup) {
    await this._notifyGroupAdmins(unsubscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_UNSUBSCRIBED, initiatorIntId, null, unsubscribedGroup.intId);
    });
  }

  static async onGroupAdminPromoted(initiatorIntId, group, newAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === newAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_PROMOTED, initiatorIntId, newAdminIntId, group.intId);
    });
  }

  static async onGroupAdminDemoted(initiatorIntId, group, formerAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === formerAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_DEMOTED, initiatorIntId, formerAdminIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestCreated(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIPTION_REQUEST, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestRevoked(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_REQUEST_REVOKED, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestApproved(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
  }

  static async onGroupSubscriptionRequestRejected(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_REJECTED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_REJECTED, null, requesterIntId, group.intId);
  }

  static async onPostCreated(post, destinationFeedIds, author) {
    const destinationFeeds = await dbAdapter.getTimelinesByIds(destinationFeedIds);
    await this._processDirectMessagesForPost(post, destinationFeeds, author);
    await this._processMentionsInPost(post, destinationFeeds, author);
  }

  static async onCommentCreated(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor) {
    const postDestinationFeeds = await post.getPostedTo();
    await this._processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
    await this._processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
  }

  ////////////////////////////////////////////

  static async _processDirectMessagesForPost(post, destinationFeeds, author) {
    const directFeeds = destinationFeeds.filter((f) => {
      return f.isDirects() && f.userId !== author.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      const directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);
      const promises = directReceivers.map((receiver) => {
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_CREATED, author.intId, receiver.intId, null, post.id, null, author.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInPost(post, destinationFeeds, author) {
    const mentionedUsernames = _.uniq(extractMentions(post.body));

    if (mentionedUsernames.length === 0) {
      return;
    }

    let postGroupIntId = null;
    if (destinationFeeds.length === 1) {
      const postFeed = destinationFeeds[0];
      const feedOwner = await postFeed.getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const usersBannedByPostAuthor = await author.getBanIds();
    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentionedUsernames);
    const promises = mentionedUsers.map(async (user) => {
      if (!user || user.type !== 'user') {
        return null;
      }

      if (author.id === user.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(user.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await user.getBanIds();
      if (usersBannedByCurrentUser.includes(author.id)) {
        return null;
      }

      const isVisible = await post.canShow(user.id);
      if (!isVisible) {
        return null;
      }

      return dbAdapter.createEvent(user.intId, EVENT_TYPES.MENTION_IN_POST, author.intId, user.intId, postGroupIntId, post.id, null, author.intId);
    });
    await Promise.all(promises);
  }

  static async _processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    const feeds = postDestinationFeeds;
    const directFeeds = feeds.filter((f) => {
      return f.isDirects() && f.userId !== commentAuthor.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      let directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);

      directReceivers = directReceivers.filter((r) => {
        return !usersBannedByCommentAuthor.includes(r.id);
      });

      const promises = directReceivers.map(async (receiver) => {
        let usersBannedByReceiver = [];
        if (receiver.id === commentAuthor.id) {
          usersBannedByReceiver = usersBannedByCommentAuthor;
        } else {
          if (receiver.id === postAuthor.id) {
            usersBannedByReceiver = usersBannedByPostAuthor;
          } else {
            usersBannedByReceiver = await receiver.getBanIds();
          }
        }

        if (usersBannedByReceiver.includes(commentAuthor.id)) {
          return null;
        }
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_COMMENT_CREATED, commentAuthor.intId, receiver.intId, null, post.id, comment.id, postAuthor.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    let mentions = extractMentionsWithIndices(comment.body);

    if (mentions.length == 0) {
      return;
    }

    let  postGroupIntId = null;
    const feeds = postDestinationFeeds;
    if (feeds.length === 1) {
      const feedOwner = await feeds[0].getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const postDestinationsFeedsOwners = feeds.map((f) => f.userId);
    const nonDirectFeeds = feeds.filter((f) => !f.isDirects());
    const nonDirectFeedsIds = nonDirectFeeds.map((f) => f.id);
    const nonDirectFeedsOwnerIds = nonDirectFeeds.map((f) => f.userId);
    const postIsPublic = await dbAdapter.someUsersArePublic(nonDirectFeedsOwnerIds, false);

    const replyToUser = mentions.find((m) => { return m.indices[0] === 0; });

    if (replyToUser) {
      _.remove(mentions, (m) => { return m.username == replyToUser.username && m.indices[0] != 0; });
    }
    mentions = _.uniqBy(mentions, 'username');

    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentions.map((m) => m.username));
    let usersSubscriptionsStatus = [];
    if (!postIsPublic) {
      usersSubscriptionsStatus = await dbAdapter.areUsersSubscribedToOneOfTimelines(mentionedUsers.map((u) => u.id), nonDirectFeedsIds);
    }
    const promises = mentions.map(async (m) => {
      const mentionedUser = mentionedUsers.find((u) => u.username === m.username);
      if (!mentionedUser || mentionedUser.type !== 'user') {
        return null;
      }

      if (commentAuthor.id === mentionedUser.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(mentionedUser.id) || usersBannedByCommentAuthor.includes(mentionedUser.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await mentionedUser.getBanIds();
      if (usersBannedByCurrentUser.includes(commentAuthor.id) || usersBannedByCurrentUser.includes(postAuthor.id)) {
        return null;
      }

      if (!postDestinationsFeedsOwners.includes(mentionedUser.id)) {
        if (nonDirectFeeds.length === 0) {
          return null;
        }

        if (!postIsPublic) {
          const subscriptionStatus = usersSubscriptionsStatus.find((u) => u.uid === mentionedUser.id);
          if (!subscriptionStatus.is_subscribed) {
            return null;
          }
        }
      }

      if (m.indices[0] === 0) {
        return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_COMMENT_TO, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
      }
      return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_IN_COMMENT, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
    });
    await Promise.all(promises);
  }

  static async _notifyGroupAdmins(group, adminNotifier) {
    const groupAdminsIds = await dbAdapter.getGroupAdministratorsIds(group.id);
    const admins = await dbAdapter.getUsersByIds(groupAdminsIds);

    const promises = admins.map((adminUser) => {
      return adminNotifier(adminUser);
    });
    await Promise.all(promises);
  }
}
<commit_msg>Reduce number of SQL queries in EventService.onPostCreated().<commit_after>import _ from 'lodash'
import { dbAdapter } from '../models'
import { extractMentions, extractMentionsWithIndices } from './mentions'

export const EVENT_TYPES = {
  MENTION_IN_POST:               'mention_in_post',
  MENTION_IN_COMMENT:            'mention_in_comment',
  MENTION_COMMENT_TO:            'mention_comment_to',
  USER_BANNED:                   'banned_user',
  USER_UNBANNED:                 'unbanned_user',
  BANNED_BY:                     'banned_by_user',
  UNBANNED_BY:                   'unbanned_by_user',
  USER_SUBSCRIBED:               'user_subscribed',
  USER_UNSUBSCRIBED:             'user_unsubscribed',
  SUBSCRIPTION_REQUESTED:        'subscription_requested',
  SUBSCRIPTION_REQUEST_REVOKED:  'subscription_request_revoked',
  SUBSCRIPTION_REQUEST_APPROVED: 'subscription_request_approved',
  SUBSCRIPTION_REQUEST_REJECTED: 'subscription_request_rejected',
  GROUP_CREATED:                 'group_created',
  GROUP_SUBSCRIBED:              'group_subscribed',
  GROUP_UNSUBSCRIBED:            'group_unsubscribed',
  GROUP_SUBSCRIPTION_REQUEST:    'group_subscription_requested',
  GROUP_REQUEST_REVOKED:         'group_subscription_request_revoked',
  GROUP_SUBSCRIPTION_APPROVED:   'group_subscription_approved',
  GROUP_SUBSCRIPTION_REJECTED:   'group_subscription_rejected',
  GROUP_ADMIN_PROMOTED:          'group_admin_promoted',
  GROUP_ADMIN_DEMOTED:           'group_admin_demoted',
  DIRECT_CREATED:                'direct',
  DIRECT_COMMENT_CREATED:        'direct_comment',

  MANAGED_GROUP_SUBSCRIPTION_APPROVED: 'managed_group_subscription_approved',
  MANAGED_GROUP_SUBSCRIPTION_REJECTED: 'managed_group_subscription_rejected',
};

export class EventService {
  static async onUserBanned(initiatorIntId, bannedUserIntId, wasSubscribed = false, hasRequestedSubscription = false) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_BANNED, initiatorIntId, bannedUserIntId);
    await dbAdapter.createEvent(bannedUserIntId, EVENT_TYPES.BANNED_BY, initiatorIntId, bannedUserIntId);
    if (wasSubscribed) {
      await this.onUserUnsubscribed(bannedUserIntId, initiatorIntId);
    }
    if (hasRequestedSubscription) {
      await this.onSubscriptionRequestRejected(bannedUserIntId, initiatorIntId);
    }
  }

  static async onUserUnbanned(initiatorIntId, unbannedUserIntId) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_UNBANNED, initiatorIntId, unbannedUserIntId);
    await dbAdapter.createEvent(unbannedUserIntId, EVENT_TYPES.UNBANNED_BY, initiatorIntId, unbannedUserIntId);
  }

  static async onUserSubscribed(initiatorIntId, subscribedUserIntId) {
    await dbAdapter.createEvent(subscribedUserIntId, EVENT_TYPES.USER_SUBSCRIBED, initiatorIntId, subscribedUserIntId);
  }

  static async onUserUnsubscribed(initiatorIntId, unsubscribedUserIntId) {
    await dbAdapter.createEvent(unsubscribedUserIntId, EVENT_TYPES.USER_UNSUBSCRIBED, initiatorIntId, unsubscribedUserIntId);
  }

  static async onSubscriptionRequestCreated(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUESTED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRevoked(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REVOKED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestApproved(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED, toUserIntId, fromUserIntId);
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.USER_SUBSCRIBED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRejected(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REJECTED, toUserIntId, fromUserIntId);
  }

  static async onGroupCreated(ownerIntId, groupIntId) {
    await dbAdapter.createEvent(ownerIntId, EVENT_TYPES.GROUP_CREATED, ownerIntId, null, groupIntId);
  }

  static async onGroupSubscribed(initiatorIntId, subscribedGroup) {
    await this._notifyGroupAdmins(subscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIBED, initiatorIntId, null, subscribedGroup.intId);
    });
  }

  static async onGroupUnsubscribed(initiatorIntId, unsubscribedGroup) {
    await this._notifyGroupAdmins(unsubscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_UNSUBSCRIBED, initiatorIntId, null, unsubscribedGroup.intId);
    });
  }

  static async onGroupAdminPromoted(initiatorIntId, group, newAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === newAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_PROMOTED, initiatorIntId, newAdminIntId, group.intId);
    });
  }

  static async onGroupAdminDemoted(initiatorIntId, group, formerAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === formerAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_DEMOTED, initiatorIntId, formerAdminIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestCreated(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIPTION_REQUEST, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestRevoked(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_REQUEST_REVOKED, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestApproved(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
  }

  static async onGroupSubscriptionRequestRejected(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_REJECTED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_REJECTED, null, requesterIntId, group.intId);
  }

  static async onPostCreated(post, destinationFeedIds, author) {
    const destinationFeeds = await dbAdapter.getTimelinesByIds(destinationFeedIds);
    await this._processDirectMessagesForPost(post, destinationFeeds, author);
    await this._processMentionsInPost(post, destinationFeeds, author);
  }

  static async onCommentCreated(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor) {
    const postDestinationFeeds = await post.getPostedTo();
    await this._processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
    await this._processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
  }

  ////////////////////////////////////////////

  static async _processDirectMessagesForPost(post, destinationFeeds, author) {
    const directFeeds = destinationFeeds.filter((f) => {
      return f.isDirects() && f.userId !== author.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      const directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);
      const promises = directReceivers.map((receiver) => {
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_CREATED, author.intId, receiver.intId, null, post.id, null, author.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInPost(post, destinationFeeds, author) {
    const mentionedUsernames = _.uniq(extractMentions(post.body));

    if (mentionedUsernames.length === 0) {
      return;
    }

    let postGroupIntId = null;
    if (destinationFeeds.length === 1) {
      const postFeed = destinationFeeds[0];
      const feedOwner = await postFeed.getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const postDestinationsFeedsOwners = destinationFeeds.map((f) => f.userId);
    const nonDirectFeeds = destinationFeeds.filter((f) => !f.isDirects());
    const nonDirectFeedsIds = nonDirectFeeds.map((f) => f.id);
    const nonDirectFeedsOwnerIds = nonDirectFeeds.map((f) => f.userId);
    const postIsPublic = await dbAdapter.someUsersArePublic(nonDirectFeedsOwnerIds, false);

    const usersBannedByPostAuthor = await author.getBanIds();
    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentionedUsernames);

    let usersSubscriptionsStatus = [];
    if (!postIsPublic) {
      usersSubscriptionsStatus = await dbAdapter.areUsersSubscribedToOneOfTimelines(mentionedUsers.map((u) => u.id), nonDirectFeedsIds);
    }

    const promises = mentionedUsers.map(async (user) => {
      if (!user || user.type !== 'user') {
        return null;
      }

      if (author.id === user.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(user.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await user.getBanIds();
      if (usersBannedByCurrentUser.includes(author.id)) {
        return null;
      }

      if (!postDestinationsFeedsOwners.includes(user.id)) {
        if (nonDirectFeeds.length === 0) {
          return null;
        }

        if (!postIsPublic) {
          const subscriptionStatus = usersSubscriptionsStatus.find((u) => u.uid === user.id);
          if (!subscriptionStatus.is_subscribed) {
            return null;
          }
        }
      }

      return dbAdapter.createEvent(user.intId, EVENT_TYPES.MENTION_IN_POST, author.intId, user.intId, postGroupIntId, post.id, null, author.intId);
    });
    await Promise.all(promises);
  }

  static async _processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    const feeds = postDestinationFeeds;
    const directFeeds = feeds.filter((f) => {
      return f.isDirects() && f.userId !== commentAuthor.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      let directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);

      directReceivers = directReceivers.filter((r) => {
        return !usersBannedByCommentAuthor.includes(r.id);
      });

      const promises = directReceivers.map(async (receiver) => {
        let usersBannedByReceiver = [];
        if (receiver.id === commentAuthor.id) {
          usersBannedByReceiver = usersBannedByCommentAuthor;
        } else {
          if (receiver.id === postAuthor.id) {
            usersBannedByReceiver = usersBannedByPostAuthor;
          } else {
            usersBannedByReceiver = await receiver.getBanIds();
          }
        }

        if (usersBannedByReceiver.includes(commentAuthor.id)) {
          return null;
        }
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_COMMENT_CREATED, commentAuthor.intId, receiver.intId, null, post.id, comment.id, postAuthor.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    let mentions = extractMentionsWithIndices(comment.body);

    if (mentions.length == 0) {
      return;
    }

    let  postGroupIntId = null;
    const feeds = postDestinationFeeds;
    if (feeds.length === 1) {
      const feedOwner = await feeds[0].getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const postDestinationsFeedsOwners = feeds.map((f) => f.userId);
    const nonDirectFeeds = feeds.filter((f) => !f.isDirects());
    const nonDirectFeedsIds = nonDirectFeeds.map((f) => f.id);
    const nonDirectFeedsOwnerIds = nonDirectFeeds.map((f) => f.userId);
    const postIsPublic = await dbAdapter.someUsersArePublic(nonDirectFeedsOwnerIds, false);

    const replyToUser = mentions.find((m) => { return m.indices[0] === 0; });

    if (replyToUser) {
      _.remove(mentions, (m) => { return m.username == replyToUser.username && m.indices[0] != 0; });
    }
    mentions = _.uniqBy(mentions, 'username');

    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentions.map((m) => m.username));
    let usersSubscriptionsStatus = [];
    if (!postIsPublic) {
      usersSubscriptionsStatus = await dbAdapter.areUsersSubscribedToOneOfTimelines(mentionedUsers.map((u) => u.id), nonDirectFeedsIds);
    }
    const promises = mentions.map(async (m) => {
      const mentionedUser = mentionedUsers.find((u) => u.username === m.username);
      if (!mentionedUser || mentionedUser.type !== 'user') {
        return null;
      }

      if (commentAuthor.id === mentionedUser.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(mentionedUser.id) || usersBannedByCommentAuthor.includes(mentionedUser.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await mentionedUser.getBanIds();
      if (usersBannedByCurrentUser.includes(commentAuthor.id) || usersBannedByCurrentUser.includes(postAuthor.id)) {
        return null;
      }

      if (!postDestinationsFeedsOwners.includes(mentionedUser.id)) {
        if (nonDirectFeeds.length === 0) {
          return null;
        }

        if (!postIsPublic) {
          const subscriptionStatus = usersSubscriptionsStatus.find((u) => u.uid === mentionedUser.id);
          if (!subscriptionStatus.is_subscribed) {
            return null;
          }
        }
      }

      if (m.indices[0] === 0) {
        return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_COMMENT_TO, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
      }
      return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_IN_COMMENT, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
    });
    await Promise.all(promises);
  }

  static async _notifyGroupAdmins(group, adminNotifier) {
    const groupAdminsIds = await dbAdapter.getGroupAdministratorsIds(group.id);
    const admins = await dbAdapter.getUsersByIds(groupAdminsIds);

    const promises = admins.map((adminUser) => {
      return adminNotifier(adminUser);
    });
    await Promise.all(promises);
  }
}
<|endoftext|>


<commit_before>import * as pickler from '../common/pickler';

export const extend = (intOps, intTypes, typeName = 'Fraction') => {

  class Fraction {
    constructor(numer, denom) {
      this.numer = numer;
      this.denom = denom;
    }

    toString() {
      return this.numer.toString() + '/' + this.denom.toString();
    }

    get __typeName() { return typeName; }
  };

  pickler.register(
    typeName,
    ({ numer, denom }) =>
      ({ numer: pickler.pickle(numer), denom: pickler.pickle(denom) }),
    ({ numer, denom }) =>
      new Fraction(pickler.unpickle(numer), pickler.unpickle(denom))
  );

  const toJS = n => intOps.toJS(n.numer) / intOps.toJS(n.denom);

  const make = (numer, denom) => {
    if (intOps.lt(denom, 0))
      return make(intOps.negative(numer), intOps.negative(denom));
    else if (intOps.eq(denom, 0))
      throw new Error('fraction has zero denominator');
    else if (intOps.eq(numer, 0))
      return numer;
    else {
      const a = intOps.gcd(denom, numer);

      if (intOps.eq(a, denom))
        return intOps.idiv(numer, a);
      else
        return new Fraction(intOps.idiv(numer, a), intOps.idiv(denom, a));
    }
  };

  const parse = s => {
    const parts = s.split('/');
    if (parts.length == 1)
      return intOps.integer(parts[0]);
    else if (parts.length == 2)
      return make(intOps.integer(parts[0]), intOps.integer(parts[1]));
    else
      throw new Error(`expected an integer or fraction literal, got ${s}`);
  };

  const promote = n => new Fraction(n, 1);

  const negative = q => new Fraction(intOps.negative(q.numer), q.denom);
  const abs      = q => new Fraction(intOps.abs(q.numer), q.denom);
  const sgn      = q => intOps.sgn(q.numer);

  const floor = q => intOps.idiv(q.numer, q.denom);

  const ceil = q => {
    const [n, r] = intOps.divmod(q.numer, q.denom);
    if (intOps.gt(r, 0))
      return intOps.plus(n, 1);
    else
      return n;
  };

  const round = q => {
    const [n, r] = intOps.divmod(q.numer, q.denom);
    if (intOps.ge(intOps.times(2, r), q.denom))
      return intOps.plus(n, 1);
    else
      return n;
  };

  const cmp = (q, r) => {
    const a = intOps.gcd(q.denom, r.denom);
    const s = intOps.idiv(r.denom, a);
    const t = intOps.idiv(q.denom, a);

    return intOps.cmp(intOps.times(s, q.numer), intOps.times(t, r.numer));
  };

  const plus = (q, r) => {
    const a = intOps.gcd(q.denom, r.denom);
    const s = intOps.idiv(r.denom, a);
    const t = intOps.idiv(q.denom, a);

    return make(intOps.plus(intOps.times(s, q.numer), intOps.times(t, r.numer)),
                intOps.times(s, q.denom));
  };

  const minus = (q, r) => plus(q, negative(r));

  const times = (q, r) => {
    const a = intOps.gcd(q.numer, r.denom);
    const b = intOps.gcd(q.denom, r.numer);

    return make(intOps.times(intOps.idiv(q.numer, a), intOps.idiv(r.numer, b)),
                intOps.times(intOps.idiv(q.denom, b), intOps.idiv(r.denom, a)));
  };

  const div = (q, r) => times(q, new Fraction(r.denom, r.numer));

  const idiv = (q, r) => {
    const t = div(q, r);

    if (intOps.typeOf(t) == typeName)
      return sgn(r) < 0 ? ceil(t): floor(t);
    else
      return sgn(r) < 0 ? intOps.ceil(t) : intOps.floor(t);
  };

  const methods = {
    __context__: () => `fractions(${intOps.__context__()})`,

    isRational: { [typeName]: x => true },
    isReal    : { [typeName]: x => true },
    toJS      : { [typeName]: toJS     },
    negative  : { [typeName]: negative },
    abs       : { [typeName]: abs      },
    sgn       : { [typeName]: sgn      },
    floor     : { [typeName]: floor    },
    ceil      : { [typeName]: ceil     },
    round     : { [typeName]: round    }
  };

  methods.rational = { String: parse };
  for (const intType of intTypes)
    methods.rational[intType] = x => x;

  for (const [op, name] of [
    [cmp  , 'cmp'  ],
    [plus , 'plus' ],
    [minus, 'minus'],
    [times, 'times'],
    [div  , 'div'  ],
    [idiv , 'idiv' ]
  ]) {
    methods[name] = { [typeName]: { [typeName]: op } };

    for (const intType of intTypes) {
      methods[name][typeName][intType] = (x, y) => op(x, promote(y));
      methods[name][intType] = { [typeName]: (x, y) => op(promote(x), y) };
    }
  }

  for (const t1 of intTypes) {
    for (const t2 of intTypes) {
      methods.div[t1][t2] = (x, y) => make(x, y);
    }
  }

  return intOps.register(methods);
};

if (require.main == module) {
  const a = require('./integers').extend(require('./base').arithmetic());
  const ops = extend(a, ['Integer', 'LongInt']);
  const timer = require('../common/util').timer();

  const N = 128;
  let t = 0, q = 1;

  for (let i = 0; i < 128; ++i) {
    q = ops.div(q, 2);
    t = ops.plus(t, q);
  }
  console.log(`${t}`);
  console.log(`${ops.plus(t, q)}`);

  console.log();
  console.log(`floor( 5/3) = ${ops.floor(ops.div( 5, 3))}`);
  console.log(`floor(-5/3) = ${ops.floor(ops.div(-5, 3))}`);
  console.log(`ceil ( 5/3) = ${ops.ceil (ops.div( 5, 3))}`);
  console.log(`ceil (-5/3) = ${ops.ceil (ops.div(-5, 3))}`);
  console.log(`round( 5/3) = ${ops.round(ops.div( 5, 3))}`);
  console.log(`round(-5/3) = ${ops.round(ops.div(-5, 3))}`);

  console.log(`${ops.rational('-12_345_678_901_234_567_890')}`);
  console.log(`${ops.rational('-111_111_111_111_111_111/-12_345_679')}`);

  console.log();
  console.log(`Computation time: ${timer()} msec`);
}
<commit_msg>fractions: tweak the make() function<commit_after>import * as pickler from '../common/pickler';

export const extend = (intOps, intTypes, typeName = 'Fraction') => {

  class Fraction {
    constructor(numer, denom) {
      this.numer = numer;
      this.denom = denom;
    }

    toString() {
      return this.numer.toString() + '/' + this.denom.toString();
    }

    get __typeName() { return typeName; }
  };

  pickler.register(
    typeName,
    ({ numer, denom }) =>
      ({ numer: pickler.pickle(numer), denom: pickler.pickle(denom) }),
    ({ numer, denom }) =>
      new Fraction(pickler.unpickle(numer), pickler.unpickle(denom))
  );

  const toJS = n => intOps.toJS(n.numer) / intOps.toJS(n.denom);

  const make = (numer, denom) => {
    if (intOps.lt(denom, 0))
      return make(intOps.negative(numer), intOps.negative(denom));
    else if (intOps.eq(denom, 0))
      throw new Error('fraction has zero denominator');
    else if (intOps.eq(numer, 0))
      return numer;
    else {
      const [q, r] = intOps.divmod(numer, denom);

      if (intOps.eq(r, 0))
        return q;
      else {
        const a = intOps.gcd(denom, numer);
        return new Fraction(intOps.idiv(numer, a), intOps.idiv(denom, a));
      }
    }
  };

  const parse = s => {
    const parts = s.split('/');
    if (parts.length == 1)
      return intOps.integer(parts[0]);
    else if (parts.length == 2)
      return make(intOps.integer(parts[0]), intOps.integer(parts[1]));
    else
      throw new Error(`expected an integer or fraction literal, got ${s}`);
  };

  const promote = n => new Fraction(n, 1);

  const negative = q => new Fraction(intOps.negative(q.numer), q.denom);
  const abs      = q => new Fraction(intOps.abs(q.numer), q.denom);
  const sgn      = q => intOps.sgn(q.numer);

  const floor = q => intOps.idiv(q.numer, q.denom);

  const ceil = q => {
    const [n, r] = intOps.divmod(q.numer, q.denom);
    if (intOps.gt(r, 0))
      return intOps.plus(n, 1);
    else
      return n;
  };

  const round = q => {
    const [n, r] = intOps.divmod(q.numer, q.denom);
    if (intOps.ge(intOps.times(2, r), q.denom))
      return intOps.plus(n, 1);
    else
      return n;
  };

  const cmp = (q, r) => {
    const a = intOps.gcd(q.denom, r.denom);
    const s = intOps.idiv(r.denom, a);
    const t = intOps.idiv(q.denom, a);

    return intOps.cmp(intOps.times(s, q.numer), intOps.times(t, r.numer));
  };

  const plus = (q, r) => {
    const a = intOps.gcd(q.denom, r.denom);
    const s = intOps.idiv(r.denom, a);
    const t = intOps.idiv(q.denom, a);

    return make(intOps.plus(intOps.times(s, q.numer), intOps.times(t, r.numer)),
                intOps.times(s, q.denom));
  };

  const minus = (q, r) => plus(q, negative(r));

  const times = (q, r) => {
    const a = intOps.gcd(q.numer, r.denom);
    const b = intOps.gcd(q.denom, r.numer);

    return make(intOps.times(intOps.idiv(q.numer, a), intOps.idiv(r.numer, b)),
                intOps.times(intOps.idiv(q.denom, b), intOps.idiv(r.denom, a)));
  };

  const div = (q, r) => times(q, new Fraction(r.denom, r.numer));

  const idiv = (q, r) => {
    const t = div(q, r);

    if (intOps.typeOf(t) == typeName)
      return sgn(r) < 0 ? ceil(t): floor(t);
    else
      return sgn(r) < 0 ? intOps.ceil(t) : intOps.floor(t);
  };

  const methods = {
    __context__: () => `fractions(${intOps.__context__()})`,

    isRational: { [typeName]: x => true },
    isReal    : { [typeName]: x => true },
    toJS      : { [typeName]: toJS     },
    negative  : { [typeName]: negative },
    abs       : { [typeName]: abs      },
    sgn       : { [typeName]: sgn      },
    floor     : { [typeName]: floor    },
    ceil      : { [typeName]: ceil     },
    round     : { [typeName]: round    }
  };

  methods.rational = { String: parse };
  for (const intType of intTypes)
    methods.rational[intType] = x => x;

  for (const [op, name] of [
    [cmp  , 'cmp'  ],
    [plus , 'plus' ],
    [minus, 'minus'],
    [times, 'times'],
    [div  , 'div'  ],
    [idiv , 'idiv' ]
  ]) {
    methods[name] = { [typeName]: { [typeName]: op } };

    for (const intType of intTypes) {
      methods[name][typeName][intType] = (x, y) => op(x, promote(y));
      methods[name][intType] = { [typeName]: (x, y) => op(promote(x), y) };
    }
  }

  for (const t1 of intTypes) {
    for (const t2 of intTypes) {
      methods.div[t1][t2] = (x, y) => make(x, y);
    }
  }

  return intOps.register(methods);
};

if (require.main == module) {
  const a = require('./integers').extend(require('./base').arithmetic());
  const ops = extend(a, ['Integer', 'LongInt']);
  const timer = require('../common/util').timer();

  const N = 128;
  let t = 0, q = 1;

  for (let i = 0; i < 128; ++i) {
    q = ops.div(q, 2);
    t = ops.plus(t, q);
  }
  console.log(`${t}`);
  console.log(`${ops.plus(t, q)}`);

  console.log();
  console.log(`floor( 5/3) = ${ops.floor(ops.div( 5, 3))}`);
  console.log(`floor(-5/3) = ${ops.floor(ops.div(-5, 3))}`);
  console.log(`ceil ( 5/3) = ${ops.ceil (ops.div( 5, 3))}`);
  console.log(`ceil (-5/3) = ${ops.ceil (ops.div(-5, 3))}`);
  console.log(`round( 5/3) = ${ops.round(ops.div( 5, 3))}`);
  console.log(`round(-5/3) = ${ops.round(ops.div(-5, 3))}`);

  console.log(`${ops.rational('-12_345_678_901_234_567_890')}`);
  console.log(`${ops.rational('-111_111_111_111_111_111/-12_345_679')}`);

  console.log();
  console.log(`Computation time: ${timer()} msec`);
}
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  grunt.option('stack', true);

  var port = process.env.PORT || 8000,
      liveReloadPort = 35729,
      hostname = 'localhost',
      templates = {},
      paths = {
        'public': 'public',
        output: {
          js: 'public/js',
          css: 'public/css'
        },
        js: 'js',
        css: 'css',
        templates: 'js/templates',
        views: 'js/views',
        models: 'js/models',
        collections: 'js/collections'
      };

  // Register required tasks
  grunt.loadTasks('tasks');
  grunt.loadNpmTasks('thorax-inspector');
  require('matchdep').filterDev('grunt-*').forEach(function(task) {
    if (task !== 'grunt-cli') {
      grunt.loadNpmTasks(task);
    }
  });

  grunt.config.init({
    pkg: grunt.file.readJSON('package.json'),
    paths: paths,
    clean: {
      output: [
        paths.output.js,
        paths.output.css
      ]
    },
    copy: {
      requirejs: {
        files: [
          {
            src: 'bower_components/requirejs/require.js',
            dest: 'public/js/require.js'
          }
        ]
      },
      styles: {
        files: [
          {
            expand: true,
            cwd: paths.css,
            src: '*.css',
            dest: paths.output.css
          }
        ]
      }<% if (includeBootstrap) { %>,
      bootstrap: {
        files: [
          {
            src: ['bower_components/bootstrap/dist/js/bootstrap.js'],
            dest: 'public/js/bootstrap.js'
          },
          {
            src: ['bower_components/bootstrap/dist/css/bootstrap.css'],
            dest: 'public/css/bootstrap.css'
          },
          {
            expand: true,
            src: ['bower_components/bootstrap/fonts/*'],
            dest: 'public/fonts'
          }
        ]
      }<% } %>
    },
    connect: {
      development: {
        options: {
          hostname: hostname,
          base: paths.public,
          port: port,
          middleware: function (connect, options) {
            return [
              require('connect-livereload')({port: liveReloadPort}),
              connect['static'](options.base),
              connect.directory(options.base)
            ];
          }
        }
      },
      production:  {
        options: {
          hostname: hostname,
          base: paths.public,
          port: port,
          keepalive: true
        }
      }
    },
    thorax: {
      inspector: {
        editor: 'subl',
        background: true,
        paths: {
          views: paths.views,
          models: paths.models,
          collections: paths.collections,
          templates: paths.templates
        }
      }
    },
    requirejs: {
      development: getRequireJSOptions('development'),
      production: getRequireJSOptions('production')
    },
    handlebars: {
      templates: {
        options: {
          namespace: false,
          amd: true
        }
      }
    <% if (includeCoffeeScript) { %>},
    coffee: {
      glob_to_multiple: {
        expand: true,
        flatten: true,
        cwd: paths.js,
        src: ['*.coffee'],
        dest: 'public/js/',
        ext: '.js'
      }<% } %>
    },<% if (styleProcessor === 'sass') { %>
    sass: {
      dist: {
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.scss'],
          dest: paths.output.css,
          ext: '.css'
        }]
      }
    },<% } %><% if (styleProcessor === 'less') { %>
    less: {
      development: {
        options: {},
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.less'],
          dest: paths.output.css,
          ext: '.css'
        }]
      },
      production: {
        options: {},
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.less'],
          dest: paths.output.css,
          ext: '.css'
        }]
      }
    },<% } %><% if (styleProcessor === 'stylus') { %>
    stylus: {
      compile: {
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.styl'],
          dest: paths.output.css,
          ext: '.css'
        }]
      }
    },<% } %>
    watch: {
      options: {
        livereload: liveReloadPort,
        files: ['public/**/*']
      },
      handlebars: {
        files: [paths.templates + '/**/*.hbs'],
        tasks: ['templates']
      <% if (includeCoffeeScript) { %>},
      coffee: {
        files: paths.js + '/**/*.coffee',
        tasks: ["coffee"]
      <% } %>},
      scripts: {
        files: [
          paths.js + '/**/*.js'
        ],
        tasks: ['scripts:development']
      },
      styles: {
        files: [paths.css + '/**/*'],
        tasks: ['styles']
      }
    }
  });

  function getRequireJSOptions(env) {
    var options = {
      appDir: paths.js,
      baseUrl: './',
      dir: paths.output.js,
      modules: [
        {
          name: 'main'
        }
      ],
      paths: {
        <% if (!useZepto) { %>'jquery': '../bower_components/jquery/jquery',<% } %>
        <% if (useZepto) { %>'zepto': '../bower_components/zepto/zepto',<% } %>
        'underscore': '../bower_components/underscore/underscore',
        'handlebars': '../bower_components/handlebars/handlebars.runtime',
        'backbone': '../bower_components/backbone/backbone',
        'thorax': '../bower_components/thorax/thorax',
        'bootstrap': '../bower_components/bootstrap/js/bootstrap'
      }<% if (includeCoffeeScript) { %>,
      packages: [
        // include the requirejs coffeescript plugin and the coffeescript compiler
        {
          name: 'cs',
          location: '../../bower_components/require-cs',
          main: 'cs'
        },
        {
          name: 'coffee-script',
          location: '../../bower_components/require-cs',
          main: 'coffee-script'
        }
      ],
      //Stub out the cs module after a build since
      //it will not be needed.
      stubModules: ['cs']<% } %>,
      shim: {
        'handlebars': {
          exports: 'Handlebars'
        },
        'backbone': {
          exports: 'Backbone',
        <% if (!useZepto) { %>deps: ['jquery', 'underscore']<% } %>
        <% if (useZepto) { %>deps: ['zepto', 'underscore']<% } %>
        },
        'underscore': {
          exports: '_'
        },
        'thorax': {
          exports: 'Thorax',
          deps: ['handlebars', 'backbone']
        },
        'bootstrap': {
        <% if (!useZepto) { %>deps: ['jquery']<% } %>
        <% if (useZepto) { %>deps: ['zepto']<% } %>
        }
        <% if (useZepto) { %>
        ,
        'zepto': {
          exports: '$'
        }
        <% } %>
      }
    };
    if (env === 'production') {
      /*
      TODO
      options.keepBuildDir = true;
      options.optimize = 'uglify';
      */
    }
    if (env === 'development') {
      options.keepBuildDir = true;
      options.optimize = 'none';
    }
    return {
      options: options
    };
  }

  grunt.registerTask('open-browser', function () {
    var open = require('open');
    open('http://' + hostname + ':' + port);
  });

  grunt.registerTask('scripts:development', [
    'copy:requirejs',
    'requirejs:development'
  ]);

  grunt.registerTask('scripts:production', [
    'copy:requirejs',
    'requirejs:production'
  ]);

  grunt.registerTask('update-templates-list', function() {
    // Set up the templates object for Handlebars
    grunt.file.glob.sync(paths.templates + '/**/*.{handlebars,hbs}').forEach(function (file) {
      var target = paths.output.js + '/templates' + file.substr(paths.templates.length).replace(/\.(?:handlebars|hbs)$/, '.js');
      templates[target] = file;
    });
    grunt.config.set('handlebars.templates.files', templates);
  });

  grunt.registerTask('create-output-directories', function() {
    grunt.file.mkdir('public/js');
    grunt.file.mkdir('public/css');
  });

  grunt.registerTask('templates', [
    'update-templates-list',
    'handlebars:templates'
  ]);

  grunt.registerTask('styles', [
    'copy:styles'<% if (includeBootstrap) { %>,
    'copy:bootstrap'<% } %><% if (styleProcessor !== 'none') { %>,
    <%= styleProcessor %><% } %>
  ]);

  grunt.registerTask('default', [
    'ensure-installed',
    'clean:output',
    'create-output-directories',
    'styles',
    'templates',
    'scripts:development',
    // 'thorax:inspector', // shared port 35729 w/ livereload
    'connect:development',
    'open-browser',
    'watch'
  ]);

  grunt.registerTask('production', [
    'clean:output',
    'create-output-directories',
    'styles',
    'templates',
    'scripts:production',
    'open-browser',
    'connect:production'
  ]);
};
<commit_msg>Update _Gruntfile.js<commit_after>module.exports = function(grunt) {
  grunt.option('stack', true);

  var port = process.env.PORT || 8000,
      liveReloadPort = 35729,
      hostname = 'localhost',
      templates = {},
      paths = {
        'public': 'public',
        output: {
          js: 'public/js',
          css: 'public/css'
        },
        js: 'js',
        css: 'css',
        templates: 'js/templates',
        views: 'js/views',
        models: 'js/models',
        collections: 'js/collections'
      };

  // Register required tasks
  grunt.loadTasks('tasks');
  grunt.loadNpmTasks('thorax-inspector');
  require('matchdep').filterDev('grunt-*').forEach(function(task) {
    if (task !== 'grunt-cli') {
      grunt.loadNpmTasks(task);
    }
  });

  grunt.config.init({
    pkg: grunt.file.readJSON('package.json'),
    paths: paths,
    clean: {
      output: [
        paths.output.js,
        paths.output.css
      ]
    },
    copy: {
      requirejs: {
        files: [
          {
            src: 'bower_components/requirejs/require.js',
            dest: 'public/js/require.js'
          }
        ]
      },
      styles: {
        files: [
          {
            expand: true,
            cwd: paths.css,
            src: '*.css',
            dest: paths.output.css
          }
        ]
      }<% if (includeBootstrap) { %>,
      bootstrap: {
        files: [
          {
            src: ['bower_components/bootstrap/dist/js/bootstrap.js'],
            dest: 'public/js/bootstrap.js'
          },
          {
            src: ['bower_components/bootstrap/dist/css/bootstrap.css'],
            dest: 'public/css/bootstrap.css'
          },
          {
            expand: true,
            src: ['bower_components/bootstrap/fonts/*'],
            dest: 'public/fonts'
          }
        ]
      }<% } %>
    },
    connect: {
      development: {
        options: {
          hostname: hostname,
          base: paths.public,
          port: port,
          middleware: function (connect, options) {
            return [
              require('connect-livereload')({port: liveReloadPort}),
              connect['static'](options.base),
              connect.directory(options.base)
            ];
          }
        }
      },
      production:  {
        options: {
          hostname: hostname,
          base: paths.public,
          port: port,
          keepalive: true
        }
      }
    },
    thorax: {
      inspector: {
        editor: 'subl',
        background: true,
        paths: {
          views: paths.views,
          models: paths.models,
          collections: paths.collections,
          templates: paths.templates
        }
      }
    },
    requirejs: {
      development: getRequireJSOptions('development'),
      production: getRequireJSOptions('production')
    },
    handlebars: {
      templates: {
        options: {
          namespace: false,
          amd: true
        }
      }
    <% if (includeCoffeeScript) { %>},
    coffee: {
      glob_to_multiple: {
        expand: true,
        flatten: true,
        cwd: paths.js,
        src: ['*.coffee'],
        dest: 'public/js/',
        ext: '.js'
      }<% } %>
    },<% if (styleProcessor === 'sass') { %>
    sass: {
      dist: {
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.scss'],
          dest: paths.output.css,
          ext: '.css'
        }]
      }
    },<% } %><% if (styleProcessor === 'less') { %>
    less: {
      development: {
        options: {},
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.less'],
          dest: paths.output.css,
          ext: '.css'
        }]
      },
      production: {
        options: {},
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.less'],
          dest: paths.output.css,
          ext: '.css'
        }]
      }
    },<% } %><% if (styleProcessor === 'stylus') { %>
    stylus: {
      compile: {
        files: [{
          expand: true,
          cwd: paths.css,
          src: ['*.styl'],
          dest: paths.output.css,
          ext: '.css'
        }]
      }
    },<% } %>
    watch: {
      options: {
        livereload: liveReloadPort,
        files: ['public/**/*']
      },
      handlebars: {
        files: [paths.templates + '/**/*.hbs'],
        tasks: ['templates']
      <% if (includeCoffeeScript) { %>},
      coffee: {
        files: paths.js + '/**/*.coffee',
        tasks: ["coffee"]
      <% } %>},
      scripts: {
        files: [
          paths.js + '/**/*.js'
        ],
        tasks: ['scripts:development']
      },
      styles: {
        files: [paths.css + '/**/*'],
        tasks: ['styles']
      }
    }
  });

  function getRequireJSOptions(env) {
    var options = {
      appDir: paths.js,
      baseUrl: './',
      dir: paths.output.js,
      modules: [
        {
          name: 'main'
        }
      ],
      paths: {
        <% if (!useZepto) { %>'jquery': '../bower_components/jquery/jquery',<% } %>
        <% if (useZepto) { %>'zepto': '../bower_components/zepto/zepto',<% } %>
        'underscore': '../bower_components/underscore/underscore',
        'handlebars': '../bower_components/handlebars/handlebars.runtime',
        'backbone': '../bower_components/backbone/backbone',
        'thorax': '../bower_components/thorax/thorax',
        'bootstrap': '../bower_components/bootstrap/js/bootstrap'
      }<% if (includeCoffeeScript) { %>,
      packages: [
        // include the requirejs coffeescript plugin and the coffeescript compiler
        {
          name: 'cs',
          location: '../../bower_components/require-cs',
          main: 'cs'
        },
        {
          name: 'coffee-script',
          location: '../../bower_components/require-cs',
          main: 'coffee-script'
        }
      ],
      //Stub out the cs module after a build since
      //it will not be needed.
      stubModules: ['cs']<% } %>,
      shim: {
        'handlebars': {
          exports: 'Handlebars'
        },
        'backbone': {
          exports: 'Backbone',
        <% if (!useZepto) { %>deps: ['jquery', 'underscore']<% } %>
        <% if (useZepto) { %>deps: ['zepto', 'underscore']<% } %>
        },
        'underscore': {
          exports: '_'
        },
        'thorax': {
          exports: 'Thorax',
          deps: ['handlebars', 'backbone']
        },
        'bootstrap': {
        <% if (!useZepto) { %>deps: ['jquery']<% } %>
        <% if (useZepto) { %>deps: ['zepto']<% } %>
        }
        <% if (useZepto) { %>
        ,
        'zepto': {
          exports: '$'
        }
        <% } %>
      }
    };
    if (env === 'production') {
      /*
      TODO
      options.keepBuildDir = true;
      options.optimize = 'uglify';
      */
    }
    if (env === 'development') {
      options.keepBuildDir = true;
      options.optimize = 'none';
    }
    return {
      options: options
    };
  }

  grunt.registerTask('open-browser', function () {
    var open = require('open');
    open('http://' + hostname + ':' + port);
  });

  grunt.registerTask('scripts:development', [
    'copy:requirejs',
    'requirejs:development'
  ]);

  grunt.registerTask('scripts:production', [
    'copy:requirejs',
    'requirejs:production'
  ]);

  grunt.registerTask('update-templates-list', function() {
    // Set up the templates object for Handlebars
    grunt.file.glob.sync(paths.templates + '/**/*.{handlebars,hbs}').forEach(function (file) {
      var target = paths.output.js + '/templates' + file.substr(paths.templates.length).replace(/\.(?:handlebars|hbs)$/, '.js');
      templates[target] = file;
    });
    grunt.config.set('handlebars.templates.files', templates);
  });

  grunt.registerTask('create-output-directories', function() {
    grunt.file.mkdir('public/js');
    grunt.file.mkdir('public/css');
  });

  grunt.registerTask('templates', [
    'update-templates-list',
    'handlebars:templates'
  ]);

  grunt.registerTask('styles', [
    'copy:styles'<% if (includeBootstrap) { %>,
    'copy:bootstrap'<% } %><% if (styleProcessor !== 'none') { %>,
    '<%= styleProcessor %>'<% } %>
  ]);

  grunt.registerTask('default', [
    'ensure-installed',
    'clean:output',
    'create-output-directories',
    'styles',
    'templates',
    'scripts:development',
    // 'thorax:inspector', // shared port 35729 w/ livereload
    'connect:development',
    'open-browser',
    'watch'
  ]);

  grunt.registerTask('production', [
    'clean:output',
    'create-output-directories',
    'styles',
    'templates',
    'scripts:production',
    'open-browser',
    'connect:production'
  ]);
};
<|endoftext|>


<commit_before>$(document).ready(function(){
    var homeSections = $('#frontpage').children('section');

    $.each(homeSections, function(){
        $(this).addClass('full-screen');
    });

    /**
     * set inital values for sections and offsets
     */

        // check the hash
        var hash = location.hash;
        var section = $(hash);

        // if there is no hash, use the first section (#intro)
        if (hash == '' ) {
            hash = '#intro';
            location.hash = hash;
            section = $(hash);
        }

        findSections(section);

        // allow scroll
        var disableScroll = false

        // animate the first section differently on initial view
        var initialView = false;

        if (hash == '#intro') {
            initialView = true;
        } else {
            $('.dark-corner').css('top', '-200%');
            $('.intro-layout').css('top', '-4000px').addClass('already-seen');
        }

        $('.frontnav-item').each(function() {
            if ($(this).children('a').attr('href') == hash) {
                $(this).addClass('active');
            }
        });

    /**
     * Smoothly move to the next section on "scroll"
     */

        $('html').on('mousewheel DOMMouseScroll onmousewheel touchmove scroll', function(e) {
            // don't animate when viewed on a small screen
            if (breakpoints() != 'small') {
                // if scrolling is disabled, i.e. there is already an animation don't do anything
                if (!disableScroll) {
                    // prevent defaults/don't bubble
                    if (e.target.id == 'el') return;
                    e.preventDefault();
                    e.stopPropagation();

                    // disable scroll --> throttle
                    disableScroll = true;
                    $(window).disablescroll();

                    //Determine Direction
                    if ( (e.originalEvent.wheelDelta && e.originalEvent.wheelDelta >= 0) || (e.originalEvent.detail && e.originalEvent.detail <= 0) ) {
                        // up
                        e.stopPropagation();
                        section = $(prevSection);

                        // if the scroll is back to intro
                        if (prevSection == '#intro' && nextSection !== '#concept') {
                            if (!$('.intro-layout').hasClass('already-seen')) {
                                $('.intro-layout').addClass('already-seen');
                            };

                            // reset the initial view
                            initialView = true;

                            // reverse the animation
                            $('html, body').animate({
                            scrollTop: prevOffset.top
                            }, 2000, function() {
                                location.hash = '#' + section.attr('id');
                            });

                            $('.dark-corner').animate({
                                top: '-50%',
                            }, 1000);

                            setTimeout(function() {
                                $('.intro-layout').animate({
                                    top: '0',
                                }, 3000);
                            }, 500);
                        } else {
                            $('html, body').animate({
                                scrollTop: prevOffset.top
                            }, 2000, function() {
                                location.hash = '#' + section.attr('id');
                            });
                        }
                        findSections(section);
                    } else {
                        // down
                        e.stopPropagation();
                        section = $(nextSection);

                        // if this is the first view of the first section
                        if (initialView) {

                            $('.dark-corner').animate({
                                top: '-200%',
                                // left: '-5%'
                            }, 1500);
                            setTimeout(function() {
                                $('.intro-layout').animate({
                                    top: '-4000px',
                                }, 4000);
                            }, 500);
                            setTimeout(function() {
                                $('html, body').animate({
                                    scrollTop: nextOffset.top,
                                }, 2000, function() {
                                    location.hash = '#' + section.attr('id');
                                    findSections(section);
                                });
                            }, 2000);
                            initialView = false;
                        } else {
                            $('html, body').animate({
                                scrollTop: nextOffset.top
                            }, 2000, function() {
                                location.hash = '#' + section.attr('id');
                            });
                            findSections(section);
                        }
                    }

                    // enable scrolling after 500ms
                    setTimeout(function(){
                        disableScroll = false;
                        $(window).disablescroll('undo');

                        $('.frontnav-item').each(function() {
                            $(this).removeClass('active');
                            if ($(this).children('a').attr('href') == '#' + section.attr('id')) {
                                $(this).addClass('active');
                            }
                        });
                    }, 1500);
                }
            }
        }); // scroll

    /**
     * Side Navigation clicks
     */
        $('.frontnav-item').on('click', function(e) {
            e.preventDefault();
            // disable scroll --> throttle
            disableScroll = true;
            $(window).disablescroll();

            var goto = $(this).children('a').attr('href');
            section = $(hash);

            var clickedItem = $(this); // maintain scope within setTimeout
            $(this).addClass('clicking');
            $(this).siblings().each(function() {
                $(this).removeClass('active');
            });
            setTimeout(function() {
                clickedItem.addClass('active').removeClass('clicking');
            }, 500);

            if (initialView) {
                initialView = false;

                $('.dark-corner').animate({
                    top: '-200%',
                    // left: '-5%'
                }, 1500);
                setTimeout(function() {
                    $('.intro-layout').animate({
                        top: '-4000px',
                    }, 4000);
                }, 500);

                setTimeout(function() {
                    $('html, body').animate({
                        scrollTop: $(goto).offset().top,
                    }, 2000, function() {
                        location.hash = '#' + $(goto).attr('id');
                        findSections($(goto));
                    });
                }, 2000);
            } else {
                if (goto == '#intro') {
                    if (!$('.intro-layout').hasClass('already-seen')) {
                        $('.intro-layout').addClass('already-seen');
                    };

                    // reset the initial view
                    initialView = true;

                    // reverse the animation
                    $('html, body').animate({
                        scrollTop: $(goto).offset().top,
                    }, 2000, function() {
                        location.hash = '#' + section.attr('id');
                        findSections($(goto));
                    });

                    $('.dark-corner').animate({
                        top: '-50%',
                    }, 1000);

                    setTimeout(function() {
                        $('.intro-layout').animate({
                            top: '0',
                        }, 3000);
                    }, 500);
                } else {
                    setTimeout(function() {
                        $('html, body').animate({
                            scrollTop: $(goto).offset().top,
                        }, 2000, function() {
                            location.hash = '#' + $(goto).attr('id');
                            findSections($(goto));
                        });
                    }, 100);
                    initialView = false;
                }
            }

            // enable scrolling after 1500ms
            setTimeout(function(){
                disableScroll = false;
                $(window).disablescroll('undo');
            }, 1500);
        });

});

$(window).on('load', function(){
    // initial scroll
    var offset = $(location.hash).offset();
    setTimeout(function(){
        $(window).scrollTop(offset.top);
    }, 50);
});

function findSections(section) {
    // get the previous and next section
    prevSection = (section.prev().length == 1 ) ? '#' + section.prev().attr('id') : '#' + section.attr('id');
    nextSection = (section.next().length == 1 ) ? '#' + section.next().attr('id') : '#' + section.attr('id');

    // get the offset of the prev/next sections to scroll to them
    prevOffset = $(prevSection).offset();
    nextOffset = $(nextSection).offset();
}<commit_msg>unnatural scroll only occurring in large-up now, fixes #16<commit_after>$(document).ready(function(){
    var homeSections = $('#frontpage').children('section');

    $.each(homeSections, function(){
        $(this).addClass('full-screen');
    });

    /**
     * set inital values for sections and offsets
     */

        // check the hash
        var hash = location.hash;
        var section = $(hash);

        // if there is no hash, use the first section (#intro)
        if (hash == '' ) {
            hash = '#intro';
            location.hash = hash;
            section = $(hash);
        }

        findSections(section);

        // allow scroll
        var disableScroll = false

        // animate the first section differently on initial view
        var initialView = false;

        if (hash == '#intro') {
            initialView = true;
        } else {
            $('.dark-corner').css('top', '-200%');
            $('.intro-layout').css('top', '-4000px').addClass('already-seen');
        }

        $('.frontnav-item').each(function() {
            if ($(this).children('a').attr('href') == hash) {
                $(this).addClass('active');
            }
        });

    /**
     * Smoothly move to the next section on "scroll"
     */

        $('html').on('mousewheel DOMMouseScroll onmousewheel touchmove scroll', function(e) {
            // don't animate when viewed on a small screen
            if (breakpoints() == 'large') {
                // if scrolling is disabled, i.e. there is already an animation don't do anything
                if (!disableScroll) {
                    // prevent defaults/don't bubble
                    if (e.target.id == 'el') return;
                    e.preventDefault();
                    e.stopPropagation();

                    // disable scroll --> throttle
                    disableScroll = true;
                    $(window).disablescroll();

                    //Determine Direction
                    if ( (e.originalEvent.wheelDelta && e.originalEvent.wheelDelta >= 0) || (e.originalEvent.detail && e.originalEvent.detail <= 0) ) {
                        // up
                        e.stopPropagation();
                        section = $(prevSection);

                        // if the scroll is back to intro
                        if (prevSection == '#intro' && nextSection !== '#concept') {
                            if (!$('.intro-layout').hasClass('already-seen')) {
                                $('.intro-layout').addClass('already-seen');
                            };

                            // reset the initial view
                            initialView = true;

                            // reverse the animation
                            $('html, body').animate({
                            scrollTop: prevOffset.top
                            }, 2000, function() {
                                location.hash = '#' + section.attr('id');
                            });

                            $('.dark-corner').animate({
                                top: '-50%',
                            }, 1000);

                            setTimeout(function() {
                                $('.intro-layout').animate({
                                    top: '0',
                                }, 3000);
                            }, 500);
                        } else {
                            $('html, body').animate({
                                scrollTop: prevOffset.top
                            }, 2000, function() {
                                location.hash = '#' + section.attr('id');
                            });
                        }
                        findSections(section);
                    } else {
                        // down
                        e.stopPropagation();
                        section = $(nextSection);

                        // if this is the first view of the first section
                        if (initialView) {

                            $('.dark-corner').animate({
                                top: '-200%',
                                // left: '-5%'
                            }, 1500);
                            setTimeout(function() {
                                $('.intro-layout').animate({
                                    top: '-4000px',
                                }, 4000);
                            }, 500);
                            setTimeout(function() {
                                $('html, body').animate({
                                    scrollTop: nextOffset.top,
                                }, 2000, function() {
                                    location.hash = '#' + section.attr('id');
                                    findSections(section);
                                });
                            }, 2000);
                            initialView = false;
                        } else {
                            $('html, body').animate({
                                scrollTop: nextOffset.top
                            }, 2000, function() {
                                location.hash = '#' + section.attr('id');
                            });
                            findSections(section);
                        }
                    }

                    // enable scrolling after 500ms
                    setTimeout(function(){
                        disableScroll = false;
                        $(window).disablescroll('undo');

                        $('.frontnav-item').each(function() {
                            $(this).removeClass('active');
                            if ($(this).children('a').attr('href') == '#' + section.attr('id')) {
                                $(this).addClass('active');
                            }
                        });
                    }, 1500);
                }
            }
        }); // scroll

    /**
     * Side Navigation clicks
     */
        $('.frontnav-item').on('click', function(e) {
            e.preventDefault();
            // disable scroll --> throttle
            disableScroll = true;
            $(window).disablescroll();

            var goto = $(this).children('a').attr('href');
            section = $(hash);

            var clickedItem = $(this); // maintain scope within setTimeout
            $(this).addClass('clicking');
            $(this).siblings().each(function() {
                $(this).removeClass('active');
            });
            setTimeout(function() {
                clickedItem.addClass('active').removeClass('clicking');
            }, 500);

            if (initialView) {
                initialView = false;

                $('.dark-corner').animate({
                    top: '-200%',
                    // left: '-5%'
                }, 1500);
                setTimeout(function() {
                    $('.intro-layout').animate({
                        top: '-4000px',
                    }, 4000);
                }, 500);

                setTimeout(function() {
                    $('html, body').animate({
                        scrollTop: $(goto).offset().top,
                    }, 2000, function() {
                        location.hash = '#' + $(goto).attr('id');
                        findSections($(goto));
                    });
                }, 2000);
            } else {
                if (goto == '#intro') {
                    if (!$('.intro-layout').hasClass('already-seen')) {
                        $('.intro-layout').addClass('already-seen');
                    };

                    // reset the initial view
                    initialView = true;

                    // reverse the animation
                    $('html, body').animate({
                        scrollTop: $(goto).offset().top,
                    }, 2000, function() {
                        location.hash = '#' + section.attr('id');
                        findSections($(goto));
                    });

                    $('.dark-corner').animate({
                        top: '-50%',
                    }, 1000);

                    setTimeout(function() {
                        $('.intro-layout').animate({
                            top: '0',
                        }, 3000);
                    }, 500);
                } else {
                    setTimeout(function() {
                        $('html, body').animate({
                            scrollTop: $(goto).offset().top,
                        }, 2000, function() {
                            location.hash = '#' + $(goto).attr('id');
                            findSections($(goto));
                        });
                    }, 100);
                    initialView = false;
                }
            }

            // enable scrolling after 1500ms
            setTimeout(function(){
                disableScroll = false;
                $(window).disablescroll('undo');
            }, 1500);
        });

});

$(window).on('load', function(){
    // initial scroll
    var offset = $(location.hash).offset();
    setTimeout(function(){
        $(window).scrollTop(offset.top);
    }, 50);
});

function findSections(section) {
    // get the previous and next section
    prevSection = (section.prev().length == 1 ) ? '#' + section.prev().attr('id') : '#' + section.attr('id');
    nextSection = (section.next().length == 1 ) ? '#' + section.next().attr('id') : '#' + section.attr('id');

    // get the offset of the prev/next sections to scroll to them
    prevOffset = $(prevSection).offset();
    nextOffset = $(nextSection).offset();
}<|endoftext|>


<commit_before>"use strict";

const co = require('co');
const es = require('event-stream');
const handleRequest = require('../lib/network').handleRequest;
const handleFileRequest = require('../lib/network').handleFileRequest;
const WEBMIN_LOGS_CACHE = 2000;

const WebSocketServer = require('ws').Server;

module.exports = {
    webmin: function(webminCtrl, app) {
        handleRequest(app.get.bind(app), '/webmin/summary',                   webminCtrl.summary);
        handleRequest(app.get.bind(app), '/webmin/summary/pow',               webminCtrl.powSummary);
        handleRequest(app.get.bind(app), '/webmin/currency/parameters',        webminCtrl.currencyParameters);
        handleRequest(app.get.bind(app),   '/webmin/blockchain/blocks/:count/:from', webminCtrl.blockchainBlocks);
        handleRequest(app.post.bind(app),  '/webmin/blockchain/add',           webminCtrl.blockchainAdd);
        handleRequest(app.get.bind(app),   '/webmin/logs/export/:quantity',    webminCtrl.logsExport);
        handleRequest(app.post.bind(app), '/webmin/key/preview',               webminCtrl.previewPubkey);
        handleRequest(app.get.bind(app),  '/webmin/server/reachable',          webminCtrl.isNodePubliclyReachable);
        handleRequest(app.get.bind(app),  '/webmin/server/http/upnp/open',     webminCtrl.openUPnP);
        handleRequest(app.get.bind(app),  '/webmin/server/http/upnp/regular',  webminCtrl.regularUPnP);
        handleRequest(app.get.bind(app),  '/webmin/server/preview_next',       webminCtrl.previewNext);
        handleRequest(app.post.bind(app), '/webmin/server/send_conf',          webminCtrl.sendConf);
        handleRequest(app.post.bind(app), '/webmin/server/net_conf',           webminCtrl.applyNetworkConf);
        handleRequest(app.post.bind(app), '/webmin/server/key_conf',           webminCtrl.applyNewKeyConf);
        handleRequest(app.post.bind(app), '/webmin/server/cpu_conf',           webminCtrl.applyCPUConf);
        handleRequest(app.get.bind(app),  '/webmin/server/republish_selfpeer', webminCtrl.publishANewSelfPeer);
        handleRequest(app.post.bind(app), '/webmin/server/test_sync',          webminCtrl.testPeer);
        handleRequest(app.post.bind(app), '/webmin/server/start_sync',         webminCtrl.startSync);
        handleRequest(app.get.bind(app),  '/webmin/server/auto_conf_network',  webminCtrl.autoConfNetwork);
        handleRequest(app.get.bind(app),  '/webmin/server/services/start_all', webminCtrl.startAllServices);
        handleRequest(app.get.bind(app),  '/webmin/server/services/stop_all',  webminCtrl.stopAllServices);
        handleRequest(app.get.bind(app),  '/webmin/server/reset/data',         webminCtrl.resetData);
        handleRequest(app.get.bind(app),  '/webmin/network/interfaces',        webminCtrl.listInterfaces);
        handleRequest(app.get.bind(app),  '/webmin/network/self',              webminCtrl.selfPeer);
        handleRequest(app.get.bind(app),  '/webmin/network/peers',             webminCtrl.peers);
        handleRequest(app.get.bind(app),  '/webmin/network/ws2p/info',         webminCtrl.ws2pInfo);
        handleRequest(app.get.bind(app),  '/webmin/network/ws2p/heads',        webminCtrl.ws2pHeads);
        handleRequest(app.get.bind(app),  '/webmin/network/ws2p/conf',         webminCtrl.ws2pConf);
        handleRequest(app.get.bind(app),  '/webmin/plug/modules',              webminCtrl.plugModulesList);
        handleRequest(app.get.bind(app),  '/webmin/plug/ui_modules',           webminCtrl.plugUiModulesList);
        handleRequest(app.get.bind(app),  '/webmin/plug/ui_modules/inject/:package', webminCtrl.plugUiModulesGetInjection);
        handleRequest(app.get.bind(app),  '/webmin/plug/check_access',         webminCtrl.plugCheckAccess);
        handleRequest(app.post.bind(app), '/webmin/plug/add',                  webminCtrl.plugAdd);
        handleRequest(app.post.bind(app), '/webmin/plug/rem',                  webminCtrl.plugRemove);
        handleFileRequest(app.get.bind(app),'/webmin/data/duniter_export',     webminCtrl.exportData);
        handleRequest(app.post.bind(app), '/webmin/data/duniter_import',       webminCtrl.importData);
    },
    webminWS: function(webminCtrl) {
        const logger = webminCtrl.server.logger;
        return (httpServer) => {

            // Socket for synchronization events
            let wssEvents = new WebSocketServer({
                server: httpServer,
                path: '/webmin/ws'
            });

            let lastLogs = [];
            wssEvents.on('connection', function connection(ws) {

                ws.on('message', () => {
                    wssEvents.broadcast(JSON.stringify({
                        type: 'log',
                        value: lastLogs
                    }));
                });

                wssEvents.broadcast(JSON.stringify({
                    type: 'log',
                    value: lastLogs
                }));

                // The callback which write each new log message to websocket
                logger.addCallbackLogs((level, msg, timestamp) => {
                    lastLogs.splice(0, Math.max(0, lastLogs.length - WEBMIN_LOGS_CACHE + 1));
                    lastLogs.push({
                        timestamp: timestamp,
                        level: level,
                        msg: msg
                    });
                    wssEvents.broadcast(JSON.stringify({
                        type: 'log',
                        value: [{
                            timestamp: timestamp,
                            level: level,
                            msg: msg
                        }]
                    }));
                });
            });

            wssEvents.broadcast = (data) => wssEvents.clients.forEach((client) => {
                try {
                    client.send(data);
                } catch (e) {
                    console.log(e);
                }
            });

            // Forward blocks & peers
            webminCtrl
                .pipe(es.mapSync(function(data) {
                    // Broadcast block
                    if (data.download !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'download',
                            value: data.download
                        }));
                    }
                    if (data.applied !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'applied',
                            value: data.applied
                        }));
                    }
                    if (data.sync !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'sync',
                            value: data.sync,
                            msg: (data.msg && (data.msg.message || data.msg))
                        }));
                    }
                    if (data.started !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'started',
                            value: data.started
                        }));
                    }
                    if (data.already_started !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'already_started',
                            value: data.already_started
                        }));
                    }
                    if (data.stopped !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'stopped',
                            value: data.stopped
                        }));
                    }
                    if (data.pulling !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'pulling',
                            value: data.pulling
                        }));
                    }
                    if (data.pow !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'pow',
                            value: data.pow
                        }));
                    }
                    if (data.ws2p !== undefined) {
                      co(function*() {
                        if (data.ws2p === 'heads') {
                          for (const head of data.added) {
                            const headInfos = head.message.split(':')
                            let posPubkey = 3;
                            // Gestion des différents formats
                            if (head.messageV2 && head.messageV2.match(/:2:/)) {
                                head.freeRooms = headInfos[9] + "/" + headInfos[10]
                            }
                            if (head.message.match(/:1:/)) {
                                posPubkey = 3;
                            } else {
                                posPubkey = 2;
                            }

                            const member = yield server.dal.getWrittenIdtyByPubkey(headInfos[posPubkey])
                            head.uid = member && member.uid || ''
                          }
                          wssEvents.broadcast(JSON.stringify({
                            type: 'ws2p',
                            value: data
                          }));
                        } else {
                          wssEvents.broadcast(JSON.stringify({
                            type: 'ws2p',
                            value: data
                          }));
                        }
                      })
                    }
                }));

          /******
           * BLOCKS
           */

          // Socket for synchronization events
          const server = webminCtrl.server;
          let currentBlock;
          const wssBlock = new WebSocketServer({
            server: httpServer,
            path: '/webmin/ws_block'
          });

          wssBlock.on('error', function (error) {
            logger.error('Error on WS Server');
            logger.error(error);
          });

          wssBlock.on('connection', function connection(ws) {
            co(function *() {
              currentBlock = yield server.dal.getCurrentBlockOrNull();
              if (currentBlock) {
                ws.send(JSON.stringify(currentBlock));
              }
            });
          });

          wssBlock.broadcast = (data) => wssBlock.clients.forEach((client) => {
            try {
              client.send(data);
            } catch (e) {
              logger.error('error on ws: %s', e);
            }
          });

          // Forward blocks & peers
          server
            .pipe(es.mapSync(function(data) {
              try {
                // Broadcast block
                if (data.joiners) {
                  currentBlock = data;
                  wssBlock.broadcast(JSON.stringify(currentBlock));
                }
              } catch (e) {
                logger.error('error on ws mapSync:', e);
              }
            }));
        };
    }
};
<commit_msg>[fix] freeRooms view undefined<commit_after>"use strict";

const co = require('co');
const es = require('event-stream');
const handleRequest = require('../lib/network').handleRequest;
const handleFileRequest = require('../lib/network').handleFileRequest;
const WEBMIN_LOGS_CACHE = 2000;

const WebSocketServer = require('ws').Server;

module.exports = {
    webmin: function(webminCtrl, app) {
        handleRequest(app.get.bind(app), '/webmin/summary',                   webminCtrl.summary);
        handleRequest(app.get.bind(app), '/webmin/summary/pow',               webminCtrl.powSummary);
        handleRequest(app.get.bind(app), '/webmin/currency/parameters',        webminCtrl.currencyParameters);
        handleRequest(app.get.bind(app),   '/webmin/blockchain/blocks/:count/:from', webminCtrl.blockchainBlocks);
        handleRequest(app.post.bind(app),  '/webmin/blockchain/add',           webminCtrl.blockchainAdd);
        handleRequest(app.get.bind(app),   '/webmin/logs/export/:quantity',    webminCtrl.logsExport);
        handleRequest(app.post.bind(app), '/webmin/key/preview',               webminCtrl.previewPubkey);
        handleRequest(app.get.bind(app),  '/webmin/server/reachable',          webminCtrl.isNodePubliclyReachable);
        handleRequest(app.get.bind(app),  '/webmin/server/http/upnp/open',     webminCtrl.openUPnP);
        handleRequest(app.get.bind(app),  '/webmin/server/http/upnp/regular',  webminCtrl.regularUPnP);
        handleRequest(app.get.bind(app),  '/webmin/server/preview_next',       webminCtrl.previewNext);
        handleRequest(app.post.bind(app), '/webmin/server/send_conf',          webminCtrl.sendConf);
        handleRequest(app.post.bind(app), '/webmin/server/net_conf',           webminCtrl.applyNetworkConf);
        handleRequest(app.post.bind(app), '/webmin/server/key_conf',           webminCtrl.applyNewKeyConf);
        handleRequest(app.post.bind(app), '/webmin/server/cpu_conf',           webminCtrl.applyCPUConf);
        handleRequest(app.get.bind(app),  '/webmin/server/republish_selfpeer', webminCtrl.publishANewSelfPeer);
        handleRequest(app.post.bind(app), '/webmin/server/test_sync',          webminCtrl.testPeer);
        handleRequest(app.post.bind(app), '/webmin/server/start_sync',         webminCtrl.startSync);
        handleRequest(app.get.bind(app),  '/webmin/server/auto_conf_network',  webminCtrl.autoConfNetwork);
        handleRequest(app.get.bind(app),  '/webmin/server/services/start_all', webminCtrl.startAllServices);
        handleRequest(app.get.bind(app),  '/webmin/server/services/stop_all',  webminCtrl.stopAllServices);
        handleRequest(app.get.bind(app),  '/webmin/server/reset/data',         webminCtrl.resetData);
        handleRequest(app.get.bind(app),  '/webmin/network/interfaces',        webminCtrl.listInterfaces);
        handleRequest(app.get.bind(app),  '/webmin/network/self',              webminCtrl.selfPeer);
        handleRequest(app.get.bind(app),  '/webmin/network/peers',             webminCtrl.peers);
        handleRequest(app.get.bind(app),  '/webmin/network/ws2p/info',         webminCtrl.ws2pInfo);
        handleRequest(app.get.bind(app),  '/webmin/network/ws2p/heads',        webminCtrl.ws2pHeads);
        handleRequest(app.get.bind(app),  '/webmin/network/ws2p/conf',         webminCtrl.ws2pConf);
        handleRequest(app.get.bind(app),  '/webmin/plug/modules',              webminCtrl.plugModulesList);
        handleRequest(app.get.bind(app),  '/webmin/plug/ui_modules',           webminCtrl.plugUiModulesList);
        handleRequest(app.get.bind(app),  '/webmin/plug/ui_modules/inject/:package', webminCtrl.plugUiModulesGetInjection);
        handleRequest(app.get.bind(app),  '/webmin/plug/check_access',         webminCtrl.plugCheckAccess);
        handleRequest(app.post.bind(app), '/webmin/plug/add',                  webminCtrl.plugAdd);
        handleRequest(app.post.bind(app), '/webmin/plug/rem',                  webminCtrl.plugRemove);
        handleFileRequest(app.get.bind(app),'/webmin/data/duniter_export',     webminCtrl.exportData);
        handleRequest(app.post.bind(app), '/webmin/data/duniter_import',       webminCtrl.importData);
    },
    webminWS: function(webminCtrl) {
        const logger = webminCtrl.server.logger;
        return (httpServer) => {

            // Socket for synchronization events
            let wssEvents = new WebSocketServer({
                server: httpServer,
                path: '/webmin/ws'
            });

            let lastLogs = [];
            wssEvents.on('connection', function connection(ws) {

                ws.on('message', () => {
                    wssEvents.broadcast(JSON.stringify({
                        type: 'log',
                        value: lastLogs
                    }));
                });

                wssEvents.broadcast(JSON.stringify({
                    type: 'log',
                    value: lastLogs
                }));

                // The callback which write each new log message to websocket
                logger.addCallbackLogs((level, msg, timestamp) => {
                    lastLogs.splice(0, Math.max(0, lastLogs.length - WEBMIN_LOGS_CACHE + 1));
                    lastLogs.push({
                        timestamp: timestamp,
                        level: level,
                        msg: msg
                    });
                    wssEvents.broadcast(JSON.stringify({
                        type: 'log',
                        value: [{
                            timestamp: timestamp,
                            level: level,
                            msg: msg
                        }]
                    }));
                });
            });

            wssEvents.broadcast = (data) => wssEvents.clients.forEach((client) => {
                try {
                    client.send(data);
                } catch (e) {
                    console.log(e);
                }
            });

            // Forward blocks & peers
            webminCtrl
                .pipe(es.mapSync(function(data) {
                    // Broadcast block
                    if (data.download !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'download',
                            value: data.download
                        }));
                    }
                    if (data.applied !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'applied',
                            value: data.applied
                        }));
                    }
                    if (data.sync !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'sync',
                            value: data.sync,
                            msg: (data.msg && (data.msg.message || data.msg))
                        }));
                    }
                    if (data.started !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'started',
                            value: data.started
                        }));
                    }
                    if (data.already_started !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'already_started',
                            value: data.already_started
                        }));
                    }
                    if (data.stopped !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'stopped',
                            value: data.stopped
                        }));
                    }
                    if (data.pulling !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'pulling',
                            value: data.pulling
                        }));
                    }
                    if (data.pow !== undefined) {
                        wssEvents.broadcast(JSON.stringify({
                            type: 'pow',
                            value: data.pow
                        }));
                    }
                    if (data.ws2p !== undefined) {
                      co(function*() {
                        if (data.ws2p === 'heads') {
                          for (let head of data.added) {
                            const headInfos = head.message.split(':')
                            let posPubkey = 3;
                            // Gestion des différents formats
                            if (head.messageV2 && head.messageV2.match(/:2:/)) {
                                const headV2Infos = head.message.split(':')
                                head.freeRooms = headV2Infos[9] + "/" + headV2Infos[10]
                            }
                            if (head.message.match(/:1:/)) {
                                posPubkey = 3;
                            } else {
                                posPubkey = 2;
                            }

                            const member = yield server.dal.getWrittenIdtyByPubkey(headInfos[posPubkey])
                            head.uid = member && member.uid || ''
                          }
                          wssEvents.broadcast(JSON.stringify({
                            type: 'ws2p',
                            value: data
                          }));
                        } else {
                          wssEvents.broadcast(JSON.stringify({
                            type: 'ws2p',
                            value: data
                          }));
                        }
                      })
                    }
                }));

          /******
           * BLOCKS
           */

          // Socket for synchronization events
          const server = webminCtrl.server;
          let currentBlock;
          const wssBlock = new WebSocketServer({
            server: httpServer,
            path: '/webmin/ws_block'
          });

          wssBlock.on('error', function (error) {
            logger.error('Error on WS Server');
            logger.error(error);
          });

          wssBlock.on('connection', function connection(ws) {
            co(function *() {
              currentBlock = yield server.dal.getCurrentBlockOrNull();
              if (currentBlock) {
                ws.send(JSON.stringify(currentBlock));
              }
            });
          });

          wssBlock.broadcast = (data) => wssBlock.clients.forEach((client) => {
            try {
              client.send(data);
            } catch (e) {
              logger.error('error on ws: %s', e);
            }
          });

          // Forward blocks & peers
          server
            .pipe(es.mapSync(function(data) {
              try {
                // Broadcast block
                if (data.joiners) {
                  currentBlock = data;
                  wssBlock.broadcast(JSON.stringify(currentBlock));
                }
              } catch (e) {
                logger.error('error on ws mapSync:', e);
              }
            }));
        };
    }
};
<|endoftext|>