diff --git a/packages/client-app/src/flux/stores/focused-contacts-store.coffee b/packages/client-app/src/flux/stores/focused-contacts-store.coffee deleted file mode 100644 index 83ac111cc..000000000 --- a/packages/client-app/src/flux/stores/focused-contacts-store.coffee +++ /dev/null @@ -1,137 +0,0 @@ -_ = require 'underscore' -Rx = require 'rx-lite' - -Utils = require '../models/utils' -Actions = require('../actions').default -NylasStore = require 'nylas-store' -Thread = require('../models/thread').default -Contact = require('../models/contact').default -MessageStore = require './message-store' -AccountStore = require('./account-store').default -DatabaseStore = require('./database-store').default -FocusedContentStore = require './focused-content-store' - -# A store that handles the focuses collections of and individual contacts -class FocusedContactsStore extends NylasStore - constructor: -> - @listenTo MessageStore, @_onMessageStoreChanged - @listenTo Actions.focusContact, @_onFocusContact - @_clearCurrentParticipants() - @_triggerLater = _.debounce(@trigger, 250) - @_loadCurrentParticipantThreads = _.debounce(@_loadCurrentParticipantThreads, 250) - - sortedContacts: -> @_currentContacts - - focusedContact: -> @_currentFocusedContact - - focusedContactThreads: -> @_currentParticipantThreads ? [] - - # We need to wait now for the MessageStore to grab all of the - # appropriate messages for the given thread. - - _onMessageStoreChanged: => - threadId = if MessageStore.itemsLoading() then null else MessageStore.threadId() - - # Always clear data immediately when we're showing the wrong thread - if @_currentThread and @_currentThread.id isnt threadId - @_clearCurrentParticipants() - @trigger() - - # Wait to populate until the user has stopped moving through threads. This is - # important because the FocusedContactStore powers tons of third-party extensions, - # which could do /horrible/ things when we trigger. - thread = if MessageStore.itemsLoading() then null else MessageStore.thread() - if thread and thread.id isnt @_currentThread?.id - @_currentThread = thread - @_popuateCurrentParticipants() - - # For now we take the last message - _popuateCurrentParticipants: -> - @_scoreAllParticipants() - sorted = _.sortBy(_.values(@_contactScores), "score").reverse() - @_currentContacts = _.map(sorted, (obj) -> obj.contact) - @_onFocusContact(@_currentContacts[0]) - - _clearCurrentParticipants: -> - @_contactScores = {} - @_currentContacts = [] - @_unsubFocusedContact?.dispose() - @_unsubFocusedContact = null - @_currentFocusedContact = null - @_currentThread = null - @_currentParticipantThreads = [] - - _onFocusContact: (contact) => - @_unsubFocusedContact?.dispose() - @_unsubFocusedContact = null - @_currentParticipantThreads = [] - - if contact - query = DatabaseStore.findBy(Contact, { - accountId: @_currentThread.accountId, - email: contact.email, - name: contact.name, - }) - @_unsubFocusedContact = Rx.Observable.fromQuery(query).subscribe (match) => - @_currentFocusedContact = match ? contact - @_triggerLater() - @_loadCurrentParticipantThreads(contact.email) - else - @_currentFocusedContact = null - @_triggerLater() - - _loadCurrentParticipantThreads: (email) -> - email = @_currentFocusedContact?.email - return unless email - DatabaseStore.findAll(Thread).where(Thread.attributes.participants.contains(email)).limit(100).background().then (threads = []) => - return unless @_currentFocusedContact?.email is email - @_currentParticipantThreads = threads - @trigger() - - # We score everyone to determine who's the most relevant to display in - # the sidebar. - _scoreAllParticipants: -> - score = (message, msgNum, field, multiplier) => - for contact, j in (message[field] ? []) - bonus = message[field].length - j - @_assignScore(contact, (msgNum+1) * multiplier + bonus) - - for message, msgNum in MessageStore.items() by -1 - if message.draft - score(message, msgNum, "to", 10000) - score(message, msgNum, "cc", 1000) - else - score(message, msgNum, "from", 100) - score(message, msgNum, "to", 10) - score(message, msgNum, "cc", 1) - - return @_contactScores - - # Self always gets a score of 0 - _assignScore: (contact, score=0) -> - return unless contact and contact.email - return if contact.email.trim().length is 0 - - key = Utils.toEquivalentEmailForm(contact.email) - - @_contactScores[key] ?= - contact: contact - score: score - @_calculatePenalties(contact, score) - - _calculatePenalties: (contact, score) -> - penalties = 0 - email = contact.email.toLowerCase().trim() - myEmail = AccountStore.accountForId(@_currentThread?.accountId)?.emailAddress - - if email is myEmail - # The whole thing which will penalize to zero - penalties += score - - notCommonDomain = not Utils.emailHasCommonDomain(myEmail) - sameDomain = Utils.emailsHaveSameDomain(myEmail, email) - if notCommonDomain and sameDomain - penalties += score * 0.9 - - return Math.max(penalties, 0) - -module.exports = new FocusedContactsStore diff --git a/packages/client-app/src/flux/stores/focused-contacts-store.es6 b/packages/client-app/src/flux/stores/focused-contacts-store.es6 new file mode 100644 index 000000000..f1a00552c --- /dev/null +++ b/packages/client-app/src/flux/stores/focused-contacts-store.es6 @@ -0,0 +1,174 @@ +import _ from 'underscore'; +import Rx from 'rx-lite'; +import NylasStore from 'nylas-store'; + +import Utils from '../models/utils'; +import Thread from '../models/thread'; +import Actions from '../actions'; +import Contact from '../models/contact'; +import MessageStore from './message-store'; +import AccountStore from './account-store'; +import DatabaseStore from './database-store'; + +// A store that handles the focuses collections of and individual contacts +class FocusedContactsStore extends NylasStore { + constructor() { + super() + this.listenTo(MessageStore, this._onMessageStoreChanged); + this.listenTo(Actions.focusContact, this._onFocusContact); + this._clearCurrentParticipants(); + this._triggerLater = _.debounce(this.trigger, 250); + this._loadCurrentParticipantThreads = _.debounce(this._loadCurrentParticipantThreads, 250); + } + + sortedContacts() { return this._currentContacts; } + + focusedContact() { return this._currentFocusedContact; } + + focusedContactThreads() { return this._currentParticipantThreads || []; } + + // We need to wait now for the MessageStore to grab all of the + // appropriate messages for the given thread. + + _onMessageStoreChanged = () => { + const threadId = MessageStore.itemsLoading() ? null : MessageStore.threadId(); + + // Always clear data immediately when we're showing the wrong thread + if (this._currentThread && this._currentThread.id !== threadId) { + this._clearCurrentParticipants(); + this.trigger(); + } + + // Wait to populate until the user has stopped moving through threads. This is + // important because the FocusedContactStore powers tons of third-party extensions, + // which could do /horrible/ things when we trigger. + const thread = MessageStore.itemsLoading() ? null : MessageStore.thread(); + if (thread && thread.id !== ((this._currentThread || {}).id)) { + this._currentThread = thread; + this._populateCurrentParticipants(); + } + } + + // For now we take the last message + _populateCurrentParticipants() { + this._scoreAllParticipants(); + const sorted = _.sortBy(_.values(this._contactScores), "score").reverse(); + this._currentContacts = _.map(sorted, obj => obj.contact); + return this._onFocusContact(this._currentContacts[0]); + } + + _clearCurrentParticipants() { + this._contactScores = {}; + this._currentContacts = []; + if (this._unsubFocusedContact) this._unsubFocusedContact.dispose(); + this._unsubFocusedContact = null; + this._currentFocusedContact = null; + this._currentThread = null; + this._currentParticipantThreads = []; + } + + _onFocusContact = (contact) => { + if (this._unsubFocusedContact) this._unsubFocusedContact.dispose(); + this._unsubFocusedContact = null; + this._currentParticipantThreads = []; + + if (contact) { + const query = DatabaseStore.findBy(Contact, { + accountId: this._currentThread.accountId, + email: contact.email, + name: contact.name, + }); + this._unsubFocusedContact = Rx.Observable.fromQuery(query).subscribe(match => { + this._currentFocusedContact = match || contact; + return this._triggerLater(); + }); + this._loadCurrentParticipantThreads(); + } else { + this._currentFocusedContact = null; + this._triggerLater(); + } + } + + _loadCurrentParticipantThreads() { + const currentContact = this._currentFocusedContact || {} + const email = currentContact.email; + if (!email) { + return + } + DatabaseStore.findAll(Thread) + .where(Thread.attributes.participants.contains(email)) + .limit(100).background() + .then((threads = []) => { + if (currentContact.email !== email) { + return + } + this._currentParticipantThreads = threads; + this.trigger(); + }); + } + + // We score everyone to determine who's the most relevant to display in + // the sidebar. + _scoreAllParticipants() { + const score = (message, msgNum, field, multiplier) => { + (message[field] || []).forEach((contact, j) => { + const bonus = message[field].length - j + this._assignScore(contact, (msgNum + 1) * multiplier + bonus) + }); + }; + + const iterable = MessageStore.items(); + for (let msgNum = iterable.length - 1; msgNum >= 0; msgNum--) { + const message = iterable[msgNum]; + if (message.draft) { + score(message, msgNum, "to", 10000); + score(message, msgNum, "cc", 1000); + } else { + score(message, msgNum, "from", 100); + score(message, msgNum, "to", 10); + score(message, msgNum, "cc", 1); + } + } + + return this._contactScores; + } + + // Self always gets a score of 0 + _assignScore(contact, score = 0) { + if (!contact || !contact.email) { return; } + if (contact.email.trim().length === 0) { return; } + + const key = Utils.toEquivalentEmailForm(contact.email); + + if (!this._contactScores[key]) { + this._contactScores[key] = { + contact: contact, + score: score - this._calculatePenalties(contact, score), + } + } + } + + _calculatePenalties(contact, score) { + let penalties = 0; + const email = contact.email.toLowerCase().trim(); + + const accountId = (this._currentThread || {}).accountId; + const account = AccountStore.accountForId(accountId) || {} + const myEmail = account.emailAddress + + if (email === myEmail) { + // The whole thing which will penalize to zero + penalties += score; + } + + const notCommonDomain = !Utils.emailHasCommonDomain(myEmail); + const sameDomain = Utils.emailsHaveSameDomain(myEmail, email); + if (notCommonDomain && sameDomain) { + penalties += score * 0.9; + } + + return Math.max(penalties, 0); + } +} + +export default new FocusedContactsStore();