fix(message-sidebar): New ContactCard injectable role, updated FocusedContactStore

- The FocusedContactStore was triggering too often, and leaving it up to the FullcontactStore to fetch the full Contact model for the focused contact (pulled from thread.)

The FocusedContactStore triggers more responsibly, and registering for the role "MessageListSidebar:ContactCard" now gives you the focused contact as a full database model. The whole ContactCard region also fades in and out.
This commit is contained in:
Ben Gotow 2015-09-24 18:58:53 -07:00
parent c73bc940b9
commit 42ad243824
22 changed files with 305 additions and 276 deletions

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -39,17 +39,8 @@ class GithubProfile extends React.Component
</div>
module.exports =
class GithubSidebar extends React.Component
@displayName: 'GithubSidebar'
# We're registering this component to appear in one of the app's primary
# columns, the MessageListSidebar. Each React Component in a column can
# specify a min and max width which limit the resizing behavior of the column.
@containerStyles:
maxWidth: 300
minWidth: 200
order: 2
flexShrink: 0
class GithubContactCardSection extends React.Component
@displayName: 'GithubContactCardSection'
constructor: (@props) ->
@state = @_getStateFromStores()

View file

@ -1,5 +1,5 @@
_ = require 'underscore-plus'
GithubSidebar = require "./github-sidebar"
GithubContactCardSection = require "./github-contact-card-section"
{ComponentRegistry,
WorkspaceStore} = require "nylas-exports"
@ -11,8 +11,8 @@ module.exports =
# Register our sidebar so that it appears in the Message List sidebar.
# This sidebar is to the right of the Message List in both split pane mode
# and list mode.
ComponentRegistry.register GithubSidebar,
location: WorkspaceStore.Location.MessageListSidebar
ComponentRegistry.register GithubContactCardSection,
role: "MessageListSidebar:ContactCard"
# Serialize is called when your package is about to be unmounted.
# You can return a state object that will be passed back to your package
@ -27,4 +27,4 @@ module.exports =
#
deactivate: ->
# Unregister our component
ComponentRegistry.unregister(GithubSidebar)
ComponentRegistry.unregister(GithubContactCardSection)

View file

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View file

@ -1,9 +1,6 @@
@import "ui-variables";
.sidebar-github-profile {
padding: @spacing-standard;
padding-bottom: 0;
a{ text-decoration: none; }
.logo {

View file

@ -3,7 +3,10 @@ MessageToolbarItems = require "./message-toolbar-items"
{ComponentRegistry,
MessageStore,
WorkspaceStore} = require 'nylas-exports'
SidebarThreadParticipants = require "./sidebar-thread-participants"
{SidebarContactCard,
SidebarSpacer,
SidebarContactList} = require "./sidebar-components"
ThreadStarButton = require './thread-star-button'
ThreadArchiveButton = require './thread-archive-button'
@ -23,7 +26,11 @@ module.exports =
ComponentRegistry.register MessageToolbarItems,
location: WorkspaceStore.Location.MessageList.Toolbar
ComponentRegistry.register SidebarThreadParticipants,
ComponentRegistry.register SidebarContactCard,
location: WorkspaceStore.Location.MessageListSidebar
ComponentRegistry.register SidebarSpacer,
location: WorkspaceStore.Location.MessageListSidebar
ComponentRegistry.register SidebarContactList,
location: WorkspaceStore.Location.MessageListSidebar
ComponentRegistry.register ThreadStarButton,
@ -44,7 +51,9 @@ module.exports =
ComponentRegistry.unregister ThreadArchiveButton
ComponentRegistry.unregister ThreadToggleUnreadButton
ComponentRegistry.unregister MessageToolbarItems
ComponentRegistry.unregister SidebarThreadParticipants
ComponentRegistry.unregister SidebarContactCard
ComponentRegistry.unregister SidebarSpacer
ComponentRegistry.unregister SidebarContactList
MessageStore.unregisterExtension(AutolinkerExtension)
MessageStore.unregisterExtension(TrackingPixelsExtension)

View file

@ -0,0 +1,111 @@
_ = require 'underscore'
React = require "react"
{Actions, FocusedContactsStore} = require("nylas-exports")
{TimeoutTransitionGroup,
InjectedComponentSet,
Flexbox} = require("nylas-component-kit")
class FocusedContactStorePropsContainer extends React.Component
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: =>
@unsubscribe = FocusedContactsStore.listen(@_onChange)
componentWillUnmount: =>
@unsubscribe()
render: ->
classname = "sidebar-section"
if @state.focusedContact
classname += " visible"
inner = React.cloneElement(@props.children, @state)
<div className={classname}>{inner}</div>
_onChange: =>
@setState(@_getStateFromStores())
_getStateFromStores: =>
sortedContacts: FocusedContactsStore.sortedContacts()
focusedContact: FocusedContactsStore.focusedContact()
class SidebarSpacer extends React.Component
@displayName: 'SidebarSpacer'
@containerStyles:
order: 50
flex: 1
constructor: (@props) ->
render: ->
<div style={flex: 1}></div>
class SidebarContactList extends React.Component
@displayName: 'SidebarContactList'
@containerStyles:
order: 100
flexShrink: 0
constructor: (@props) ->
render: ->
<FocusedContactStorePropsContainer>
<SidebarContactListInner/>
</FocusedContactStorePropsContainer>
class SidebarContactListInner extends React.Component
constructor: (@props) ->
render: ->
<div className="sidebar-contact-list">
<h2>Thread Participants</h2>
{@_renderSortedContacts()}
</div>
_renderSortedContacts: =>
@props.sortedContacts.map (contact) =>
if contact.email is @props.focusedContact.email
selected = "selected"
else
selected = ""
<div className="contact #{selected}"
onClick={=> @_onSelectContact(contact)}
key={contact.email + contact.name}>
{contact.name}
</div>
_onSelectContact: (contact) =>
Actions.focusContact(contact)
class SidebarContactCard extends React.Component
@displayName: 'SidebarContactCard'
@containerStyles:
order: 0
flexShrink: 0
minWidth:200
maxWidth:300
constructor: (@props) ->
render: ->
<FocusedContactStorePropsContainer>
<SidebarContactCardInner />
</FocusedContactStorePropsContainer>
class SidebarContactCardInner extends React.Component
constructor: (@props) ->
render: ->
<InjectedComponentSet
className="sidebar-contact-card"
key={@props.focusedContact.email}
matching={role: "MessageListSidebar:ContactCard"}
direction="column"
exposedProps={contact: @props.focusedContact}/>
module.exports = {SidebarContactCard, SidebarSpacer, SidebarContactList}

View file

@ -1,55 +0,0 @@
_ = require 'underscore'
React = require "react"
{Actions, FocusedContactsStore} = require("nylas-exports")
class SidebarThreadParticipants extends React.Component
@displayName: 'SidebarThreadParticipants'
@containerStyles:
order: 3
flexShrink: 0
constructor: (@props) ->
@state =
@_getStateFromStores()
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.email+contact.name}>
{contact.name}
</div>
)
return contacts
_onSelectContact: (contact) =>
Actions.focusContact(contact)
_onChange: =>
@setState(@_getStateFromStores())
_getStateFromStores: =>
sortedContacts: FocusedContactsStore.sortedContacts()
focusedContact: FocusedContactsStore.focusedContact()
module.exports = SidebarThreadParticipants

View file

@ -523,22 +523,18 @@
}
///////////////////////////////
// sidebar-thread-participants.cjsx //
// sidebar-contact-card.cjsx //
///////////////////////////////
.sidebar-thread-participants {
.sidebar-section {
opacity: 0;
padding: @spacing-standard;
cursor: default;
.other-contact {
color: @text-color-subtle;
font-size: @font-size-smaller;
&.selected {
font-weight: @font-weight-semi-bold;
}
&.visible {
transition: opacity 0.1s ease-out;
opacity: 1;
}
// TODO: DRY
h2.sidebar-h2 {
h2 {
font-size: 11px;
font-weight: @font-weight-semi-bold;
text-transform: uppercase;
@ -546,8 +542,23 @@
border-bottom: 1px solid @border-color-divider;
margin: 2em 0 1em 0;
}
.sidebar-contact-card {
}
.sidebar-contact-list {
min-height:120px;
.contact {
color: @text-color-subtle;
font-size: @font-size-smaller;
&.selected {
font-weight: @font-weight-semi-bold;
}
}
}
}
.column-MessageListSidebar {
background-color: @background-off-primary;
overflow: auto;

View file

@ -7,53 +7,32 @@ request = require 'request'
DatabaseStore,
FocusedContactsStore} = require 'nylas-exports'
module.exports =
FullContactStore = Reflux.createStore
init: ->
@_loadContactDataFromAPI = _.debounce(_.bind(@__loadContactDataFromAPI, @), 50)
# @_cachedContactData = {}
@_resolvedFocusedContact = null
@_loadFocusedContact = _.debounce(_.bind(@_loadFocusedContact, @), 20)
@_loadFocusedContact()
@listenTo ContactStore, @_loadFocusedContact
@listenTo FocusedContactsStore, @_loadFocusedContact
focusedContact: -> @_resolvedFocusedContact
# We need to pull fresh from the database so when we update data in the
# for the contact, we get it anew.
_loadFocusedContact: ->
contact = FocusedContactsStore.focusedContact()
account = AccountStore.current()
if contact
@_resolvedFocusedContact = contact
DatabaseStore.findBy(Contact, {email: contact.email, accountId: account.id}).then (contact) =>
@_resolvedFocusedContact = contact
if contact and not contact.thirdPartyData?["FullContact"]?
@_loadContactDataFromAPI(contact)
@trigger()
dataForContact: (contact) ->
return {} unless contact
if contact.thirdPartyData["FullContact"]
return contact.thirdPartyData["FullContact"]
else
@_resolvedFocusedContact = null
@trigger()
@_attachFullcontactDataToContact(contact)
return {}
__loadContactDataFromAPI: (contact) ->
# Swap the url's to see real data
_attachFullcontactDataToContact: (contact) ->
email = contact.email.toLowerCase().trim()
return if email.length is 0
url = "https://api.fullcontact.com/v2/person.json?email=#{email}&apiKey=eadcbaf0286562a"
request url, (err, resp, data) =>
return {} if err
return {} if resp.statusCode != 200
return if err
return if resp.statusCode != 200
try
data = JSON.parse data
contact = @_mergeDataIntoContact(contact, data)
DatabaseStore.persistModel(contact).then => @trigger(@)
data = JSON.parse(data)
contact.title = data.organizations?[0]?["title"]
contact.company = data.organizations?[0]?["name"]
contact.thirdPartyData ?= {}
contact.thirdPartyData["FullContact"] = data
DatabaseStore.persistModel(contact)
@trigger()
_mergeDataIntoContact: (contact, data) ->
contact.title = data.organizations?[0]?["title"]
contact.company = data.organizations?[0]?["name"]
contact.thirdPartyData ?= {}
contact.thirdPartyData["FullContact"] = data
return contact
module.exports = FullContactStore

View file

@ -8,7 +8,7 @@ module.exports =
activate: (@state={}) ->
ComponentRegistry.register SidebarFullContact,
location: WorkspaceStore.Location.MessageListSidebar
role: "MessageListSidebar:ContactCard"
deactivate: ->
ComponentRegistry.unregister(SidebarFullContact)

View file

@ -9,10 +9,10 @@ class SidebarFullContactDetails extends React.Component
@propTypes:
contact: React.PropTypes.object
fullContact: React.PropTypes.object
fullContactData: React.PropTypes.object
render: =>
<div className="full-contact">
<div className="contact-card-fullcontact">
<div className="header">
{@_profilePhoto()}
<h1 className="name">{@_name()}</h1>
@ -45,7 +45,7 @@ class SidebarFullContactDetails extends React.Component
</div>
_profiles: =>
profiles = @props.fullContact.socialProfiles ? []
profiles = @props.fullContactData.socialProfiles ? []
profiles = _.filter profiles, (p) => @_supportedProfileTypes[p.typeId]
_supportedProfileTypes:
@ -85,7 +85,7 @@ class SidebarFullContactDetails extends React.Component
@_title().length > 0 or @_company().length > 0
_name: =>
(@props.fullContact.contactInfo?.fullName) ? @props.contact?.name ? ""
(@props.fullContactData.contactInfo?.fullName) ? @props.contact?.name ? ""
_email: =>
email = @props.contact.email ? ""
@ -103,7 +103,7 @@ class SidebarFullContactDetails extends React.Component
else return ""
_company: =>
location = @props.fullContact.demographics?.locationGeneral ? ""
location = @props.fullContactData.demographics?.locationGeneral ? ""
name = @_primaryOrg()?.name ? ""
if name.length > 0 and location.length > 0
return "#{name} (#{location})"
@ -114,13 +114,13 @@ class SidebarFullContactDetails extends React.Component
else return ""
_primaryOrg: =>
orgs = @props.fullContact.organizations ? []
orgs = @props.fullContactData.organizations ? []
org = _.findWhere orgs, isPrimary: true
if not org? then org = orgs[0]
return org
_profilePhoto: =>
photos = @props.fullContact.photos ? []
photos = @props.fullContactData.photos ? []
photo = _.findWhere photo, isPrimary: true
if not photo? then photo = _.findWhere photo, typeId: "linkedin"
if not photo? then photo = photos[0]

View file

@ -2,47 +2,35 @@ _ = require 'underscore'
React = require "react"
FullContactStore = require "./fullcontact-store"
{InjectedComponentSet} = require 'nylas-component-kit'
{InjectedComponentSet, TimeoutTransitionGroup} = require 'nylas-component-kit'
SidebarFullContactDetails = require "./sidebar-fullcontact-details"
class SidebarFullContact extends React.Component
@displayName: "SidebarFullContact"
@containerStyles:
order: 1
maxWidth: 300
minWidth: 200
flexShrink: 0
@propTypes:
contact: React.PropTypes.object
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: =>
@unsubscribe = FullContactStore.listen @_onChange
@unsubscribe = FullContactStore.listen(@_onChange)
componentWillUnmount: =>
@unsubscribe()
render: =>
<div className="full-contact-sidebar">
<SidebarFullContactDetails contact={@state.focusedContact ? {}}
fullContact={@_fullContact()}/>
<InjectedComponentSet matching={role: "sidebar:focusedContactInfo"}
direction="column"
exposedProps={focusedContact: @state.focusedContact}/>
</div>
_fullContact: =>
if @state.focusedContact?.thirdPartyData
return @state.focusedContact?.thirdPartyData["FullContact"] ? {}
else
return {}
<SidebarFullContactDetails
contact={@props.contact}
fullContactData={@state.focusedContactData} />
_onChange: =>
@setState(@_getStateFromStores())
_getStateFromStores: =>
focusedContact: FullContactStore.focusedContact()
focusedContactData: FullContactStore.dataForContact(@props.contact)
module.exports = SidebarFullContact

View file

@ -1,84 +1,72 @@
@import "ui-variables";
.full-contact-sidebar {
padding: @spacing-standard;
.contact-card-fullcontact {
padding-bottom: 0;
order: 1;
flex-shrink: 0;
.full-contact {
color: @text-color;
-webkit-user-select:text;
img.content-mask { background-color: @text-color; }
color: @text-color;
-webkit-user-select:text;
img.content-mask { background-color: @text-color; }
h1.name {
font-size: 20px;
font-weight: @font-weight-normal;
margin: 0;
padding: 0.6em 0 0.4em 0;
}
.profile-photo {
max-width: 42px;
max-height: 42px;
display: block;
float: right;
margin-left: @spacing-standard;
}
.header {
&:before, &:after { content: " "; display: table; }
&:after { clear: both; }
}
.subheader {
color: @text-color-subtle;
padding: 0 0 @spacing-standard 0;
font-size: @font-size-smaller;
}
.email {
word-break: break-all;
}
h1.name {
font-size: 20px;
font-weight: @font-weight-normal;
margin: 0;
padding: 0.6em 0 0.4em 0;
text-overflow: ellipsis;
overflow: hidden;
}
.social-profiles {
border-top: 1px solid @border-color-divider;
padding-top: 7px;
}
.social-profile {
margin-top: 0.5em;
.social-icon {
margin-top: 6px;
float: left;
}
.social-link {
padding-left: @spacing-double;
font-size: @font-size-smaller;
a {
text-decoration: none;
}
}
.profile-photo {
max-width: 42px;
max-height: 42px;
display: block;
float: right;
margin-left: @spacing-standard;
}
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;
.header {
&:before, &:after { content: " "; display: table; }
&:after { clear: both; }
}
.sidebar-extra-info {
font-size: 10px;
font-weight: @font-weight-medium;
.subheader {
color: @text-color-subtle;
}
.sidebar-no-info {
padding: 0 0 @spacing-standard 0;
font-size: @font-size-smaller;
color: fade(@text-color, 30%);
}
.email {
word-break: break-all;
}
}
.social-profiles {
border-top: 1px solid @border-color-divider;
padding-top: 7px;
}
.social-profile {
margin-top: 0.5em;
.social-icon {
margin-top: 6px;
float: left;
}
.social-link {
padding-left: @spacing-double;
font-size: @font-size-smaller;
a {
text-decoration: none;
}
}
}
.sidebar-extra-info {
font-size: 12px;
font-weight: @font-weight-medium;
color: @text-color-subtle;
}
.sidebar-no-info {
font-size: @font-size-smaller;
color: fade(@text-color, 30%);
}

View file

@ -35,6 +35,7 @@ Section: Models
###
class Contact extends Model
constructor: ->
@thirdPartyData ?= {}
super
@attributes: _.extend {}, Model.attributes,

View file

@ -3,65 +3,78 @@ _ = require 'underscore'
Utils = require '../models/utils'
Actions = require '../actions'
NylasStore = require 'nylas-store'
Contact = require '../models/contact'
MessageStore = require './message-store'
AccountStore = require './account-store'
DatabaseStore = require './database-store'
FocusedContentStore = require './focused-content-store'
# A store that handles the focuses collections of and individual contacts
class FocusedContactsStore extends NylasStore
constructor: ->
@listenTo Actions.focusContact, @_focusContact
@listenTo DatabaseStore, @_onDatabaseChanged
@listenTo MessageStore, @_onMessageStoreChanged
@listenTo AccountStore, @_onAccountChanged
@listenTo FocusedContentStore, @_onFocusChanged
@_currentThread = null
@_clearCurrentParticipants(silent: true)
@_onAccountChanged()
@listenTo Actions.focusContact, @_onFocusContact
@_clearCurrentParticipants()
sortedContacts: -> @_currentContacts
focusedContact: -> @_currentFocusedContact
_clearCurrentParticipants: ({silent}={}) ->
@_contactScores = {}
@_currentContacts = []
@_currentFocusedContact = null
@trigger() unless silent
# We need to wait now for the MessageStore to grab all of the
# appropriate messages for the given thread.
_onFocusChanged: (change) =>
return unless change.impactsCollection('thread')
item = FocusedContentStore.focused('thread')
return if @_currentThread?.id is item?.id
@_currentThread = item
@_clearCurrentParticipants()
@_onMessageStoreChanged()
# We need to wait now for the MessageStore to grab all of the
# appropriate messages for the given thread.
_onDatabaseChanged: (change) =>
return unless @_currentFocusedContact
return unless change and change.objectClass is 'contact'
current = _.find change.objects, (c) => c.email is @_currentFocusedContact.email
if current
@_currentFocusedContact = current
@trigger()
_onMessageStoreChanged: =>
if MessageStore.threadId() is @_currentThread?.id
@_setCurrentParticipants()
else
@_clearCurrentParticipants()
threadId = if MessageStore.itemsLoading() then null else MessageStore.threadId()
_onAccountChanged: =>
@_myEmail = (AccountStore.current()?.me().email ? "").toLowerCase().trim()
# 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.
@_onMessageStoreChangeThrottled ?= _.debounce =>
thread = if MessageStore.itemsLoading() then null else MessageStore.thread()
if thread and thread.id isnt @_currentThread?.id
@_currentThread = thread
@_popuateCurrentParticipants()
, 250
@_onMessageStoreChangeThrottled()
# For now we take the last message
_setCurrentParticipants: ->
_popuateCurrentParticipants: ->
@_scoreAllParticipants()
sorted = _.sortBy(_.values(@_contactScores), "score").reverse()
@_currentContacts = _.map(sorted, (obj) -> obj.contact)
@_focusContact(@_currentContacts[0], silent: true)
@trigger()
@_onFocusContact(@_currentContacts[0])
_focusContact: (contact, {silent}={}) =>
return unless contact
@_currentFocusedContact = contact
@trigger() unless silent
_clearCurrentParticipants: ->
@_contactScores = {}
@_currentContacts = []
@_currentFocusedContact = null
@_currentThread = null
_onFocusContact: (contact) =>
if not contact
@_currentFocusedContact = null
@trigger()
else
DatabaseStore.findBy(Contact, {
email: contact.email,
accountId: @_currentThread.accountId
}).then (match) =>
@_currentFocusedContact = match ? contact
@trigger()
# We score everyone to determine who's the most relevant to display in
# the sidebar.
@ -79,37 +92,34 @@ class FocusedContactsStore extends NylasStore
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 unless contact and contact.email
return if contact.email.trim().length is 0
return if @_contactScores[contact.toString()]? # only assign the first time
penalties = @_calculatePenalties(contact, score)
key = Utils.toEquivalentEmailForm(contact.email)
@_contactScores[contact.toString()] =
@_contactScores[key] ?=
contact: contact
score: score - penalties
score: score - @_calculatePenalties(contact, score)
_calculatePenalties: (contact, score) ->
penalties = 0
email = contact.email.toLowerCase().trim()
myEmail = AccountStore.current().emailAddress
if email is @_myEmail
penalties += score # The whole thing which will penalize to zero
if email is myEmail
# The whole thing which will penalize to zero
penalties += score
notCommonDomain = not Utils.emailHasCommonDomain(@_myEmail)
sameDomain = Utils.emailsHaveSameDomain(@_myEmail, email)
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
module.exports = new FocusedContactsStore

View file

@ -140,6 +140,7 @@ class MessageStore extends NylasStore
_fetchFromCache: (options = {}) ->
return unless @_thread
loadedThreadId = @_thread.id
query = DatabaseStore.findAll(Message)

View file

@ -12,7 +12,7 @@ module.exports =
role: 'Composer:ActionButton'
ComponentRegistry.register MyMessageSidebar,
role: 'sidebar:focusedContactInfo'
role: 'MessageListSidebar:ContactCard'
# Serialize is called when your package is about to be unmounted.
# You can return a state object that will be passed back to your package

View file

@ -12,8 +12,6 @@ class MyMessageSidebar extends React.Component
# these values.
@containerStyles:
order: 1
maxWidth: 300
minWidth: 200
flexShrink: 0
# This sidebar component listens to the FocusedContactStore,

View file

@ -9,7 +9,7 @@ describe "activate", ->
spyOn(ComponentRegistry, 'register')
activate()
expect(ComponentRegistry.register).toHaveBeenCalledWith(MyComposerButton, {role: 'Composer:ActionButton'})
expect(ComponentRegistry.register).toHaveBeenCalledWith(MyMessageSidebar, {role: 'sidebar:focusedContactInfo'})
expect(ComponentRegistry.register).toHaveBeenCalledWith(MyMessageSidebar, {role: 'MessageListSidebar:ContactCard'})
describe "deactivate", ->
it "should unregister the composer button and sidebar", ->