Add ability to select account to send from

- Adds settings option to select default account to send from
This commit is contained in:
Juan Tejada 2016-01-25 18:20:19 -08:00
parent 205ec50b98
commit 324123731a
11 changed files with 136 additions and 58 deletions

View file

@ -1,5 +1,6 @@
React = require 'react'
_ = require 'underscore'
React = require 'react'
classnames = require 'classnames'
{AccountStore} = require 'nylas-exports'
{Menu, ButtonDropdown} = require 'nylas-component-kit'
@ -9,41 +10,52 @@ class AccountContactField extends React.Component
@propTypes:
value: React.PropTypes.object
account: React.PropTypes.object,
accounts: React.PropTypes.array.isRequired
onChange: React.PropTypes.func.isRequired
render: =>
<div className="composer-participant-field">
<div className="composer-field-label">{"From:"}</div>
{@_renderFromPicker()}
</div>
_onChooseContact: (contact) =>
accountId = contact.accountId
from = [contact]
@props.onChange({accountId, from})
@refs.dropdown.toggleDropdown()
_renderFromPicker: ->
if @props.account? && @props.value?
label = @props.value.toString()
if @props.account.aliases.length is 0
return @_renderAccountSpan(label)
return <ButtonDropdown
_renderAccountSelector: ->
return <span /> unless @props.value
label = @props.value.toString()
multipleAccounts = @props.accounts.length > 1
hasAliases = @props.accounts[0]?.aliases.length > 0
if multipleAccounts or hasAliases
<ButtonDropdown
ref="dropdown"
bordered={false}
primaryItem={<span>{label}</span>}
menu={@_renderAliasesMenu(@props.account)}/>
menu={@_renderAccounts(@props.accounts)} />
else
return @_renderAccountSpan("Please select an account")
@_renderAccountSpan(label)
_renderAliasesMenu: (account) =>
_renderMenuItem: (contact) =>
className = classnames(
'contact': true
'is-alias': contact.isAlias
)
<span className={className}>{contact.toString()}</span>
_renderAccounts: (accounts) =>
items = AccountStore.aliasesFor(accounts)
<Menu
items={[account.me().toString()].concat account.aliases}
itemKey={ (alias) -> alias }
itemContent={ (alias) -> alias }
onSelect={@_onChooseAlias.bind(@, account)} />
items={items}
itemKey={(contact) -> contact.id}
itemContent={@_renderMenuItem}
onSelect={@_onChooseContact} />
_renderAccountSpan: (label) ->
<span className="from-picker" style={position: "relative", top: 6, left: "0.5em"}>{label}</span>
_onChooseAlias: (account, alias) =>
@props.onChange(account.meUsingAlias(alias))
@refs.dropdown.toggleDropdown()
render: =>
<div className="composer-participant-field">
<div className="composer-field-label">From:</div>
{@_renderAccountSelector()}
</div>
module.exports = AccountContactField

View file

@ -62,10 +62,11 @@ class ComposerView extends React.Component
to: []
cc: []
bcc: []
from: []
body: ""
files: []
subject: ""
account: null
accounts: []
focusedField: Fields.To # Gets updated in @_initiallyFocusedField
enabledFields: [] # Gets updated in @_initiallyEnabledFields
showQuotedText: false
@ -223,7 +224,7 @@ class ComposerView extends React.Component
from={@state.from}
ref="expandedParticipants"
mode={@props.mode}
account={@state.account}
accounts={@state.accounts}
focusedField={@state.focusedField}
enabledFields={@state.enabledFields}
onPopoutComposer={@_onPopoutComposer}
@ -529,7 +530,7 @@ class ComposerView extends React.Component
body: draft.body
files: draft.files
subject: draft.subject
account: AccountStore.accountForId(draft.accountId)
accounts: @_getAccounts()
if !@state.populated
_.extend state,
@ -576,21 +577,25 @@ class ComposerView extends React.Component
enabledFields.push Fields.Body
return enabledFields
_getAccounts: =>
if @props.mode is 'inline'
[AccountStore.accountForId(@_proxy.draft().accountId)]
else
AccountStore.accounts()
# When the account store changes, the From field may or may not still
# be in scope. We need to make sure to update our enabled fields.
_onAccountStoreChanged: =>
accounts = @_getAccounts()
enabledFields = if @_shouldShowFromField(@_proxy?.draft())
@state.enabledFields.concat [Fields.From]
else
_.without(@state.enabledFields, Fields.From)
account = AccountStore.accountForId @_proxy?.draft().accountId
@setState {enabledFields, account}
@setState {enabledFields, accounts}
_shouldShowFromField: (draft) =>
return false unless draft
account = AccountStore.accountForId(draft.accountId)
return false unless account
return account.aliases.length > 0
return true if draft
return false
_shouldEnableSubject: =>
return false unless @_proxy

View file

@ -18,7 +18,7 @@ class ExpandedParticipants extends React.Component
from: React.PropTypes.array
# The account to which the current draft belongs
account: React.PropTypes.object
accounts: React.PropTypes.array
# Either "fullwindow" or "inline"
mode: React.PropTypes.string
@ -46,6 +46,7 @@ class ExpandedParticipants extends React.Component
cc: []
bcc: []
from: []
accounts: []
enabledFields: []
constructor: (@props={}) ->
@ -144,9 +145,9 @@ class ExpandedParticipants extends React.Component
<AccountContactField
key="from"
ref={Fields.From}
onChange={ (me) => @props.onChangeParticipants(from: [me]) }
onChange={({accountId, from}) => @props.onChangeParticipants({accountId, from})}
onFocus={ => @props.onChangeFocusedField(Fields.From) }
account={@props.account}
accounts={@props.accounts}
value={@props.from?[0]} />
)

View file

@ -330,23 +330,16 @@ describe "ComposerView", ->
makeComposer.call @
expect(@composer._shouldShowFromField()).toBe false
it "disables if account has no aliases", ->
spyOn(AccountStore, 'accountForId').andCallFake -> {id: 1, aliases: []}
useDraft.call @, replyToMessageId: null, files: []
makeComposer.call @
expect(@composer.state.enabledFields).not.toContain Fields.From
it "enables if it's a reply-to message", ->
aliases = ['A <a@b.c']
spyOn(AccountStore, 'accountForId').andCallFake -> {id: 1, aliases: aliases}
spyOn(AccountStore, 'accountForId').andReturn {id: 1, aliases: aliases}
useDraft.call @, replyToMessageId: "local-123", files: []
makeComposer.call @
expect(@composer.state.enabledFields).toContain Fields.From
it "enables if requirements are met", ->
it "enables if it is not a reply-to message", ->
a1 = new Account()
a1.aliases = ['a1']
spyOn(AccountStore, 'accountForId').andCallFake -> a1
useDraft.call @, replyToMessageId: null, files: []
makeComposer.call @
expect(@composer.state.enabledFields).toContain Fields.From

View file

@ -323,6 +323,12 @@ body.platform-win32 {
.secondary-items {
border-radius: 4px;
}
.item {
.contact.is-alias {
font-style: italic;
float: right;
}
}
}
.participant {

View file

@ -5,6 +5,7 @@ _ = require 'underscore'
ConfigSchemaItem = require './config-schema-item'
WorkspaceSection = require './workspace-section'
SendingSection = require './sending-section'
class PreferencesGeneral extends React.Component
@displayName: 'PreferencesGeneral'
@ -31,11 +32,7 @@ class PreferencesGeneral extends React.Component
keyPath="core.reading"
config={@props.config} />
<ConfigSchemaItem
configSchema={@props.configSchema.properties.sending}
keyName="Sending"
keyPath="core.sending"
config={@props.config} />
<SendingSection config={@props.config} configSchema={@props.configSchema} />
<ConfigSchemaItem
configSchema={@props.configSchema.properties.attachments}

View file

@ -0,0 +1,42 @@
_ = require 'underscore'
React = require 'react'
{AccountStore} = require 'nylas-exports'
ConfigSchemaItem = require './config-schema-item'
class SendingSection extends React.Component
@displayName: 'SendingSection'
@propTypes:
config: React.PropTypes.object
configSchema: React.PropTypes.object
_getExtendedSchema: (configSchema) ->
accounts = AccountStore.accounts()
values = accounts.map (acc) -> acc.id
labels = accounts.map (acc) -> acc.me().toString()
values = [null, values...]
labels = ['Account of selected mailbox', labels...]
_.extend(configSchema.properties.sending.properties, {
defaultAccountIdForSend:
type: 'string'
title: 'Send new messages from'
default: null
enum: values
enumLabels: labels
})
return configSchema.properties.sending
render: ->
sendingSchema = @_getExtendedSchema(@props.configSchema)
<ConfigSchemaItem
config={@props.config}
configSchema={sendingSchema}
keyName="Sending"
keyPath="core.sending" />
module.exports = SendingSection

View file

@ -81,7 +81,7 @@ class Account extends Model
meUsingAlias: (alias) ->
Contact = require './contact'
return @me() unless alias
return Contact.fromString(alias)
return Contact.fromString(alias, accountId: @id)
usesLabels: ->
@organizationUnit is "label"

View file

@ -63,7 +63,7 @@ class Contact extends Model
setup: ->
['CREATE INDEX IF NOT EXISTS ContactEmailIndex ON Contact(account_id,email)']
@fromString: (string) ->
@fromString: (string, {accountId} = {}) ->
emailRegex = RegExpUtils.emailRegex()
match = emailRegex.exec(string)
if emailRegex.exec(string)
@ -73,7 +73,7 @@ class Contact extends Model
name = name[0...-1] if name[name.length - 1] in ['<', '(']
name = name.trim()
return new Contact
accountId: undefined
accountId: accountId
name: name
email: email

View file

@ -116,6 +116,21 @@ class AccountStore
accountForId: (id) =>
_.findWhere(@_accounts, {id})
aliases: () =>
aliases = []
for acc in @_accounts
aliases.push(acc.me())
for alias in acc.aliases
aliasContact = acc.meUsingAlias(alias)
aliasContact.isAlias = true
aliases.push(aliasContact)
return aliases
aliasesFor: (accountsOrIds) =>
ids = accountsOrIds.map (accOrId) ->
if accOrId instanceof Account then accOrId.id else accOrId
@aliases().filter((contact) -> contact.accountId in ids)
# Public: Returns the currently active {Account}.
current: =>
throw new Error("I can't haz the account")

View file

@ -369,10 +369,19 @@ class DraftStore
InlineStyleTransformer.run(body).then (body) =>
SanitizeTransformer.run(body, SanitizeTransformer.Preset.UnsafeOnly)
_getAccountForNewMessage: =>
defAccountId = NylasEnv.config.get('core.sending.defaultAccountIdForSend')
if defAccountId?
AccountStore.accountForId(defAccountId)
else
focusedAccountId = FocusedPerspectiveStore.current().accountIds[0]
if focusedAccountId
AccountStore.accountForId(focusedAccountId)
else
AccountStore.accounts()[0]
_onPopoutBlankDraft: =>
# TODO Remove this when we add account selector inside composer
account = FocusedPerspectiveStore.current().account
account ?= AccountStore.accounts()[0]
account = @_getAccountForNewMessage()
draft = new Message
body: ""
@ -408,9 +417,7 @@ class DraftStore
windowProps: _.extend(options, {draftClientId})
_onHandleMailtoLink: (event, urlString) =>
# TODO Remove this when we add account selector inside composer
account = FocusedPerspectiveStore.current().account
account ?= AccountStore.accounts()[0]
account = @_getAccountForNewMessage()
try
urlString = decodeURI(urlString)