mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 05:06:53 +08:00
Add ability to select account to send from
- Adds settings option to select default account to send from
This commit is contained in:
parent
b94c2e35e4
commit
b13ca724f1
11 changed files with 136 additions and 58 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]} />
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -323,6 +323,12 @@ body.platform-win32 {
|
|||
.secondary-items {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.item {
|
||||
.contact.is-alias {
|
||||
font-style: italic;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.participant {
|
||||
|
|
|
@ -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}
|
||||
|
|
42
internal_packages/preferences/lib/tabs/sending-section.cjsx
Normal file
42
internal_packages/preferences/lib/tabs/sending-section.cjsx
Normal 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
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue