mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-26 18:12:13 +08:00
feat(sidebar): add more Salesforce states
Summary: add more Salesforce states more salesforce sidebar extract focused contacts into its own store fullcontact store fixes extract thread participants to own module typo Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://review.inboxapp.com/D1326
This commit is contained in:
parent
b005d0204b
commit
564ecca8e0
16 changed files with 323 additions and 186 deletions
|
@ -46,6 +46,7 @@ module.exports =
|
|||
WorkspaceStore: require '../src/flux/stores/workspace-store'
|
||||
FileUploadStore: require '../src/flux/stores/file-upload-store'
|
||||
FileDownloadStore: require '../src/flux/stores/file-download-store'
|
||||
FocusedContactsStore: require '../src/flux/stores/focused-contacts-store'
|
||||
|
||||
## TODO move to inside of individual Salesforce package. See https://trello.com/c/tLAGLyeb/246-move-salesforce-models-into-individual-package-db-models-for-packages-various-refactors
|
||||
SalesforceAssociation: require '../src/flux/models/salesforce-association'
|
||||
|
|
|
@ -3,6 +3,7 @@ MessageList = require "./message-list"
|
|||
MessageToolbarItems = require "./message-toolbar-items"
|
||||
MessageSubjectItem = require "./message-subject-item"
|
||||
{ComponentRegistry, WorkspaceStore} = require 'inbox-exports'
|
||||
SidebarThreadParticipants = require "./sidebar-thread-participants"
|
||||
{RetinaImg} = require 'ui-components'
|
||||
|
||||
DownButton = React.createClass
|
||||
|
@ -56,6 +57,10 @@ module.exports =
|
|||
view: UpButton
|
||||
location: WorkspaceStore.Sheet.Thread.Toolbar.Right
|
||||
|
||||
ComponentRegistry.register
|
||||
name: 'SidebarThreadParticipants'
|
||||
location: WorkspaceStore.Location.MessageListSidebar
|
||||
view: SidebarThreadParticipants
|
||||
|
||||
deactivate: ->
|
||||
ComponentRegistry.unregister 'MessageToolbarItems'
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
_ = require 'underscore-plus'
|
||||
React = require "react"
|
||||
|
||||
{Actions, FocusedContactsStore} = require("inbox-exports")
|
||||
|
||||
module.exports =
|
||||
SidebarThreadParticipants = React.createClass
|
||||
displayName: 'SidebarThreadParticipants'
|
||||
|
||||
getInitialState: ->
|
||||
sortedContacts: []
|
||||
focusedContact: null
|
||||
|
||||
componentDidMount: ->
|
||||
@unsubscribe = FocusedContactsStore.listen @_onChange
|
||||
|
||||
componentWillUnmount: ->
|
||||
@unsubscribe()
|
||||
|
||||
render: ->
|
||||
<div className="sidebar-thread-participants">
|
||||
<h2 className="sidebar-h2">Thread Participants</h2>
|
||||
{@_renderSortedContacts()}
|
||||
</div>
|
||||
|
||||
_renderSortedContacts: ->
|
||||
contacts = []
|
||||
@state.sortedContacts.forEach (contact) =>
|
||||
if contact is @state.focusedContact
|
||||
selected = "selected"
|
||||
else selected = ""
|
||||
contacts.push(
|
||||
<div className="other-contact #{selected}"
|
||||
onClick={=> @_onSelectContact(contact)}
|
||||
key={contact.id}>
|
||||
{contact.name}
|
||||
</div>
|
||||
)
|
||||
return contacts
|
||||
|
||||
_onSelectContact: (contact) ->
|
||||
Actions.focusContact(contact)
|
||||
|
||||
_onChange: ->
|
||||
@setState(@_getStateFromStores())
|
||||
|
||||
_getStateFromStores: ->
|
||||
sortedContacts: FocusedContactsStore.sortedContacts()
|
||||
focusedContact: FocusedContactsStore.focusedContact()
|
|
@ -173,10 +173,6 @@
|
|||
}
|
||||
|
||||
|
||||
.column-MessageListSidebar {
|
||||
background-color: @background-off-primary;
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// message-participants.cjsx //
|
||||
///////////////////////////////
|
||||
|
@ -235,3 +231,41 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// sidebar-thread-participants.cjsx //
|
||||
///////////////////////////////
|
||||
.sidebar-thread-participants {
|
||||
padding: @spacing-standard;
|
||||
order: 10;
|
||||
flex-shrink: 0;
|
||||
|
||||
.other-contact {
|
||||
color: @text-color-subtle;
|
||||
font-size: @font-size-smaller;
|
||||
&.selected {
|
||||
font-weight: @font-weight-semi-bold;
|
||||
}
|
||||
&:hover {cursor: pointer;}
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
h2.sidebar-h2 {
|
||||
font-size: 11px;
|
||||
font-weight: @font-weight-semi-bold;
|
||||
text-transform: uppercase;
|
||||
color: @text-color-very-subtle;
|
||||
border-bottom: 1px solid @border-color-divider;
|
||||
margin: 2em 0 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.column-MessageListSidebar {
|
||||
background-color: @background-off-primary;
|
||||
overflow: auto;
|
||||
border-left: 1px solid #ddd;
|
||||
.flexbox-handle-horizontal div {
|
||||
border-right: 0;
|
||||
width: 1px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,125 +1,32 @@
|
|||
_ = require 'underscore-plus'
|
||||
Reflux = require 'reflux'
|
||||
request = require 'request'
|
||||
|
||||
{Utils,
|
||||
Actions,
|
||||
MessageStore,
|
||||
NamespaceStore} = require 'inbox-exports'
|
||||
{FocusedContactsStore} = require 'inbox-exports'
|
||||
|
||||
module.exports =
|
||||
FullContactStore = Reflux.createStore
|
||||
|
||||
init: ->
|
||||
# @listenTo Actions.getFullContactDetails, @_makeDataRequest
|
||||
@listenTo Actions.selectThreadId, @_onSelectThreadId
|
||||
@listenTo Actions.focusContact, @_focusContact
|
||||
@listenTo MessageStore, @_onMessageStoreChanged
|
||||
@listenTo NamespaceStore, @_onNamespaceChanged
|
||||
|
||||
@_cachedContactData = {}
|
||||
@listenTo FocusedContactsStore, @_onFocusedContacts
|
||||
|
||||
@_currentThreadId = null
|
||||
@_clearCurrentParticipants(silent: true)
|
||||
|
||||
@_onNamespaceChanged()
|
||||
|
||||
sortedContacts: -> @_currentContacts
|
||||
|
||||
focusedContact: -> @_currentFocusedContact
|
||||
sortedContacts: -> FocusedContactsStore.sortedContacts()
|
||||
focusedContact: -> FocusedContactsStore.focusedContact()
|
||||
|
||||
fullContactCache: ->
|
||||
emails = {}
|
||||
emails[contact.email] = contact for contact in @_currentContacts
|
||||
contacts = FocusedContactsStore.sortedContacts()
|
||||
emails[contact.email] = contact for contact in contacts
|
||||
fullContactCache = {}
|
||||
_.each @_cachedContactData, (fullContactData, email) ->
|
||||
if email of emails then fullContactCache[email] = fullContactData
|
||||
return fullContactCache
|
||||
|
||||
_clearCurrentParticipants: ({silent}={}) ->
|
||||
@_contactScores = {}
|
||||
@_currentContacts = []
|
||||
@_currentFocusedContact = null
|
||||
@trigger() unless silent
|
||||
|
||||
_onSelectThreadId: (id) ->
|
||||
@_currentThreadId = id
|
||||
@_clearCurrentParticipants()
|
||||
# We need to wait now for the MessageStore to grab all of the
|
||||
# appropriate messages for the given thread.
|
||||
|
||||
_onMessageStoreChanged: ->
|
||||
if MessageStore.threadId() is @_currentThreadId
|
||||
@_setCurrentParticipants()
|
||||
else
|
||||
@_clearCurrentParticipants()
|
||||
|
||||
_onNamespaceChanged: ->
|
||||
@_myEmail = (NamespaceStore.current()?.me().email ? "").toLowerCase().trim()
|
||||
|
||||
# For now we take the last message
|
||||
_setCurrentParticipants: ->
|
||||
@_scoreAllParticipants()
|
||||
sorted = _.sortBy(_.values(@_contactScores), "score").reverse()
|
||||
@_currentContacts = _.map(sorted, (obj) -> obj.contact)
|
||||
@_focusContact(@_currentContacts[0], silent: true)
|
||||
@trigger()
|
||||
|
||||
_focusContact: (contact, {silent}={}) ->
|
||||
return unless contact
|
||||
@_currentFocusedContact = contact
|
||||
_onFocusedContacts: ->
|
||||
contact = FocusedContactsStore.focusedContact() ? {}
|
||||
if not @_cachedContactData[contact.email]
|
||||
@_fetchAPIData(contact.email)
|
||||
@trigger() unless silent
|
||||
|
||||
# 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?.email
|
||||
return if contact.email.trim().length is 0
|
||||
return if @_contactScores[contact.nameEmail()]? # only assign the first time
|
||||
|
||||
penalties = @_calculatePenalties(contact, score)
|
||||
|
||||
@_contactScores[contact.nameEmail()] =
|
||||
contact: contact
|
||||
score: score - penalties
|
||||
|
||||
_calculatePenalties: (contact, score) ->
|
||||
penalties = 0
|
||||
email = contact.email.toLowerCase().trim()
|
||||
|
||||
if email is @_myEmail
|
||||
penalties += score # The whole thing which will penalize to zero
|
||||
|
||||
notCommonDomain = not Utils.emailHasCommonDomain(@_myEmail)
|
||||
sameDomain = Utils.emailsHaveSameDomain(@_myEmail, email)
|
||||
if notCommonDomain and sameDomain
|
||||
penalties += score * 0.9
|
||||
|
||||
return Math.max(penalties, 0)
|
||||
|
||||
_matchesDomain: (myEmail, email) ->
|
||||
myDomain = _.last(myEmail.split("@"))
|
||||
theirDomain = _.last(email.split("@"))
|
||||
return myDomain.length > 0 and theirDomain.length > 0 and myDomain is theirDomain
|
||||
@trigger()
|
||||
|
||||
_fetchAPIData: (email) ->
|
||||
# Swap the url's to see real data
|
||||
|
@ -130,6 +37,5 @@ FullContactStore = Reflux.createStore
|
|||
return {} if resp.statusCode != 200
|
||||
try
|
||||
data = JSON.parse data
|
||||
console.log data
|
||||
@_cachedContactData[email] = data
|
||||
@trigger(@)
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
_ = require 'underscore-plus'
|
||||
React = require "react"
|
||||
|
||||
{Actions} = require 'inbox-exports'
|
||||
|
||||
module.exports =
|
||||
SidebarFullContactChip = React.createClass
|
||||
|
||||
render: ->
|
||||
<div className="fullcontact-chips">
|
||||
{
|
||||
for contact in @props.contacts
|
||||
if contact.name != contact.email
|
||||
@_makeContactChip(contact, @props.compact)
|
||||
}
|
||||
{
|
||||
for contact in @props.contacts
|
||||
if contact.name == contact.email
|
||||
@_makeContactChip(contact, @props.compact)
|
||||
}
|
||||
</div>
|
||||
|
||||
_makeContactChip: (contact, compact) ->
|
||||
if contact.name == contact.email or compact == true
|
||||
<div className="fullcontact-chip" onClick={=>@props.selectContact(contact.email)} >
|
||||
<h6>{contact.email}</h6>
|
||||
</div>
|
||||
else
|
||||
<div className="fullcontact-chip" onClick={=>@props.selectContact(contact.email)} >
|
||||
{
|
||||
if compact != true
|
||||
<h3>{contact.name}</h3>
|
||||
}
|
||||
<h6>{contact.email}</h6>
|
||||
</div>
|
|
@ -2,10 +2,16 @@ _ = require 'underscore-plus'
|
|||
React = require "react"
|
||||
|
||||
{Actions} = require 'inbox-exports'
|
||||
{RetinaImg} = require 'ui-components'
|
||||
|
||||
module.exports =
|
||||
SidebarFullContactDetails = React.createClass
|
||||
|
||||
_supportedProfileTypes:
|
||||
twitter: true
|
||||
linkedin: true
|
||||
facebook: true
|
||||
|
||||
propTypes:
|
||||
contact: React.PropTypes.object
|
||||
fullContact: React.PropTypes.object
|
||||
|
@ -21,12 +27,55 @@ SidebarFullContactDetails = React.createClass
|
|||
<div className="title">{@_title()}</div>
|
||||
<div className="company">{@_company()}</div>
|
||||
</div>
|
||||
{@_renderActions()}
|
||||
<div className="social-profiles"
|
||||
style={display: if @_showSocialProfiles() then "block" else "none"}>
|
||||
{@_socialProfiles()}
|
||||
</div>
|
||||
{@_noInfo()}
|
||||
</div>
|
||||
|
||||
_renderActions: ->
|
||||
<div className="actions">
|
||||
</div>
|
||||
_socialProfiles: ->
|
||||
profiles = @_profiles()
|
||||
return profiles.map (profile) =>
|
||||
<div className="social-profile">
|
||||
<RetinaImg name="#{profile.typeId}-icon.png" className="social-icon" />
|
||||
<div className="social-link">
|
||||
<a href={profile.url}>{@_username(profile)}</a>
|
||||
{@_twitterBio(profile)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
_profiles: ->
|
||||
profiles = @props.fullContact.socialProfiles ? []
|
||||
profiles = _.filter profiles, (p) => @_supportedProfileTypes[p.typeId]
|
||||
|
||||
_showSocialProfiles: ->
|
||||
@_profiles().length > 0
|
||||
|
||||
_username: (profile) ->
|
||||
if (profile.username ? "").length > 0
|
||||
if profile.typeId is "twitter"
|
||||
return "@#{profile.username}"
|
||||
else
|
||||
return profile.username
|
||||
else
|
||||
return profile.typeName
|
||||
|
||||
_noInfo: ->
|
||||
if not @_showSocialProfiles() and not @_showSubheader()
|
||||
<div className="sidebar-no-info">No additional information available.</div>
|
||||
else return ""
|
||||
|
||||
_twitterBio: (profile) ->
|
||||
return "" unless profile.typeId is "twitter"
|
||||
return "" unless profile.bio?.length > 0
|
||||
|
||||
# http://stackoverflow.com/a/13398311/793472
|
||||
twitterRegex = /(^|[^@\w])@(\w{1,15})\b/g
|
||||
replace = '$1<a href="https://twitter.com/$2">@$2</a>'
|
||||
bio = profile.bio.replace(twitterRegex, replace)
|
||||
<div className="bio sidebar-extra-info"
|
||||
dangerouslySetInnerHTML={{__html: bio}}></div>
|
||||
|
||||
_showSubheader: ->
|
||||
@_title().length > 0 or @_company().length > 0
|
||||
|
|
|
@ -4,14 +4,11 @@ FullContactStore = require "./fullcontact-store"
|
|||
|
||||
SidebarFullContactDetails = require "./sidebar-fullcontact-details.cjsx"
|
||||
|
||||
{Actions} = require("inbox-exports")
|
||||
|
||||
module.exports =
|
||||
SidebarFullContact = React.createClass
|
||||
|
||||
getInitialState: ->
|
||||
fullContactCache: {}
|
||||
sortedContacts: []
|
||||
focusedContact: null
|
||||
|
||||
componentDidMount: ->
|
||||
|
@ -24,30 +21,8 @@ SidebarFullContact = React.createClass
|
|||
<div className="full-contact-sidebar">
|
||||
<SidebarFullContactDetails contact={@state.focusedContact ? {}}
|
||||
fullContact={@_fullContact()}/>
|
||||
<div className="other-contacts">
|
||||
<h2 className="sidebar-h2">Thread Participants</h2>
|
||||
{@_renderSortedContacts()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
_renderSortedContacts: ->
|
||||
contacts = []
|
||||
@state.sortedContacts.forEach (contact) =>
|
||||
if contact is @state.focusedContact
|
||||
selected = "selected"
|
||||
else selected = ""
|
||||
contacts.push(
|
||||
<div className="other-contact #{selected}"
|
||||
onClick={=> @_onSelectContact(contact)}
|
||||
key={contact.id}>
|
||||
{contact.name}
|
||||
</div>
|
||||
)
|
||||
return contacts
|
||||
|
||||
_onSelectContact: (contact) ->
|
||||
Actions.focusContact(contact)
|
||||
|
||||
_fullContact: ->
|
||||
if @state.focusedContact?.email
|
||||
return @state.fullContactCache[@state.focusedContact.email] ? {}
|
||||
|
@ -59,7 +34,6 @@ SidebarFullContact = React.createClass
|
|||
|
||||
_getStateFromStores: ->
|
||||
fullContactCache: FullContactStore.fullContactCache()
|
||||
sortedContacts: FullContactStore.sortedContacts()
|
||||
focusedContact: FullContactStore.focusedContact()
|
||||
|
||||
SidebarFullContact.maxWidth = 300
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
.full-contact-sidebar {
|
||||
padding: @spacing-standard;
|
||||
padding-bottom: 0;
|
||||
order: 1;
|
||||
flex-shrink: 0;
|
||||
|
||||
.full-contact {
|
||||
h1.name {
|
||||
|
@ -24,31 +26,49 @@
|
|||
}
|
||||
|
||||
.subheader {
|
||||
border-top: 1px solid @border-color-divider;
|
||||
margin: @spacing-standard 0;
|
||||
color: @text-color-subtle;
|
||||
padding: @spacing-standard 0;
|
||||
padding: 0 0 @spacing-standard 0;
|
||||
font-size: @font-size-smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.social-profiles {
|
||||
border-top: 1px solid @border-color-divider;
|
||||
padding-top: 7px;
|
||||
}
|
||||
.social-profile {
|
||||
margin-top: 0.5em;
|
||||
.social-icon {
|
||||
padding-top: 6px;
|
||||
float: left;
|
||||
}
|
||||
.social-link {
|
||||
padding-left: @spacing-double;
|
||||
font-size: @font-size-smaller;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
h2.sidebar-h2 {
|
||||
font-size: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: @font-weight-semi-bold;
|
||||
text-transform: uppercase;
|
||||
color: @text-color-very-subtle;
|
||||
border-bottom: 1px solid @border-color-divider;
|
||||
margin: 2em 0 1em 0;
|
||||
}
|
||||
|
||||
.other-contacts {
|
||||
.other-contact {
|
||||
color: @text-color-subtle;
|
||||
font-size: @font-size-smaller;
|
||||
&.selected {
|
||||
font-weight: @font-weight-semi-bold;
|
||||
}
|
||||
&:hover {cursor: pointer;}
|
||||
}
|
||||
.sidebar-extra-info {
|
||||
font-size: 10px;
|
||||
font-weight: @font-weight-medium;
|
||||
color: @text-color-subtle;
|
||||
}
|
||||
.sidebar-no-info {
|
||||
font-size: @font-size-smaller;
|
||||
color: fade(@text-color, 30%);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
'f' : 'application:forward' # Gmail
|
||||
|
||||
'escape': 'application:pop-sheet'
|
||||
'u': 'application:pop-sheet' # Gmail
|
||||
|
||||
# Default cross-platform core behaviors
|
||||
'left': 'core:move-left'
|
||||
|
|
24
spec-inbox/stores/focused-contacts-store-spec.coffee
Normal file
24
spec-inbox/stores/focused-contacts-store-spec.coffee
Normal file
|
@ -0,0 +1,24 @@
|
|||
proxyquire = require 'proxyquire'
|
||||
Reflux = require 'reflux'
|
||||
|
||||
MessageStoreStub = Reflux.createStore
|
||||
items: -> []
|
||||
threadId: -> null
|
||||
|
||||
NamespaceStoreStub = Reflux.createStore
|
||||
current: -> null
|
||||
|
||||
FocusedContactsStore = proxyquire '../../src/flux/stores/focused-contacts-store',
|
||||
"./message-store": MessageStoreStub
|
||||
"./namespace-store": NamespaceStoreStub
|
||||
|
||||
describe "FocusedContactsStore", ->
|
||||
beforeEach ->
|
||||
FocusedContactsStore._currentThreadId = null
|
||||
FocusedContactsStore._clearCurrentParticipants(silent: true)
|
||||
|
||||
it "returns no contacts with empty", ->
|
||||
expect(FocusedContactsStore.sortedContacts()).toEqual []
|
||||
|
||||
it "returns no focused contact when empty", ->
|
||||
expect(FocusedContactsStore.focusedContact()).toBeNull()
|
109
src/flux/stores/focused-contacts-store.coffee
Normal file
109
src/flux/stores/focused-contacts-store.coffee
Normal file
|
@ -0,0 +1,109 @@
|
|||
_ = require 'underscore-plus'
|
||||
Reflux = require 'reflux'
|
||||
|
||||
Utils = require '../models/utils.coffee'
|
||||
Actions = require '../actions'
|
||||
MessageStore = require './message-store'
|
||||
NamespaceStore = require './namespace-store'
|
||||
|
||||
# A store that handles the focuses collections of and individual contacts
|
||||
module.exports =
|
||||
FocusedContactsStore = Reflux.createStore
|
||||
init: ->
|
||||
@listenTo Actions.selectThreadId, @_onSelectThreadId
|
||||
@listenTo Actions.focusContact, @_focusContact
|
||||
@listenTo MessageStore, => @_onMessageStoreChanged()
|
||||
@listenTo NamespaceStore, => @_onNamespaceChanged()
|
||||
|
||||
@_currentThreadId = null
|
||||
@_clearCurrentParticipants(silent: true)
|
||||
|
||||
@_onNamespaceChanged()
|
||||
|
||||
sortedContacts: -> @_currentContacts
|
||||
|
||||
focusedContact: -> @_currentFocusedContact
|
||||
|
||||
_clearCurrentParticipants: ({silent}={}) ->
|
||||
@_contactScores = {}
|
||||
@_currentContacts = []
|
||||
@_currentFocusedContact = null
|
||||
@trigger() unless silent
|
||||
|
||||
_onSelectThreadId: (id) ->
|
||||
@_currentThreadId = id
|
||||
@_clearCurrentParticipants()
|
||||
# We need to wait now for the MessageStore to grab all of the
|
||||
# appropriate messages for the given thread.
|
||||
|
||||
_onMessageStoreChanged: ->
|
||||
if MessageStore.threadId() is @_currentThreadId
|
||||
@_setCurrentParticipants()
|
||||
else
|
||||
@_clearCurrentParticipants()
|
||||
|
||||
_onNamespaceChanged: ->
|
||||
@_myEmail = (NamespaceStore.current()?.me().email ? "").toLowerCase().trim()
|
||||
|
||||
# For now we take the last message
|
||||
_setCurrentParticipants: ->
|
||||
@_scoreAllParticipants()
|
||||
sorted = _.sortBy(_.values(@_contactScores), "score").reverse()
|
||||
@_currentContacts = _.map(sorted, (obj) -> obj.contact)
|
||||
@_focusContact(@_currentContacts[0], silent: true)
|
||||
@trigger()
|
||||
|
||||
_focusContact: (contact, {silent}={}) ->
|
||||
return unless contact
|
||||
@_currentFocusedContact = contact
|
||||
@trigger() unless silent
|
||||
|
||||
# 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?.email
|
||||
return if contact.email.trim().length is 0
|
||||
return if @_contactScores[contact.nameEmail()]? # only assign the first time
|
||||
|
||||
penalties = @_calculatePenalties(contact, score)
|
||||
|
||||
@_contactScores[contact.nameEmail()] =
|
||||
contact: contact
|
||||
score: score - penalties
|
||||
|
||||
_calculatePenalties: (contact, score) ->
|
||||
penalties = 0
|
||||
email = contact.email.toLowerCase().trim()
|
||||
|
||||
if email is @_myEmail
|
||||
penalties += score # The whole thing which will penalize to zero
|
||||
|
||||
notCommonDomain = not Utils.emailHasCommonDomain(@_myEmail)
|
||||
sameDomain = Utils.emailsHaveSameDomain(@_myEmail, email)
|
||||
if notCommonDomain and sameDomain
|
||||
penalties += score * 0.9
|
||||
|
||||
return Math.max(penalties, 0)
|
||||
|
||||
_matchesDomain: (myEmail, email) ->
|
||||
myDomain = _.last(myEmail.split("@"))
|
||||
theirDomain = _.last(email.split("@"))
|
||||
return myDomain.length > 0 and theirDomain.length > 0 and myDomain is theirDomain
|
||||
|
BIN
static/images/sidebar/facebook-icon@2x.png
Normal file
BIN
static/images/sidebar/facebook-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 622 B |
BIN
static/images/sidebar/icon-phone@2x.png
Normal file
BIN
static/images/sidebar/icon-phone@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 799 B |
BIN
static/images/sidebar/linkedin-icon@2x.png
Normal file
BIN
static/images/sidebar/linkedin-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 637 B |
BIN
static/images/sidebar/twitter-icon@2x.png
Normal file
BIN
static/images/sidebar/twitter-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 782 B |
Loading…
Reference in a new issue