From 324123731ae6ca36bc4302a8cdd532868262956b Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Mon, 25 Jan 2016 18:20:19 -0800 Subject: [PATCH] Add ability to select account to send from - Adds settings option to select default account to send from --- .../composer/lib/account-contact-field.cjsx | 58 +++++++++++-------- .../composer/lib/composer-view.cjsx | 23 +++++--- .../composer/lib/expanded-participants.cjsx | 7 ++- .../composer/spec/composer-view-spec.cjsx | 11 +--- .../composer/stylesheets/composer.less | 6 ++ .../lib/tabs/preferences-general.cjsx | 7 +-- .../preferences/lib/tabs/sending-section.cjsx | 42 ++++++++++++++ src/flux/models/account.coffee | 2 +- src/flux/models/contact.coffee | 4 +- src/flux/stores/account-store.coffee | 15 +++++ src/flux/stores/draft-store.coffee | 19 ++++-- 11 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 internal_packages/preferences/lib/tabs/sending-section.cjsx diff --git a/internal_packages/composer/lib/account-contact-field.cjsx b/internal_packages/composer/lib/account-contact-field.cjsx index 24589e5c0..dbd4ab479 100644 --- a/internal_packages/composer/lib/account-contact-field.cjsx +++ b/internal_packages/composer/lib/account-contact-field.cjsx @@ -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: => -
-
{"From:"}
- {@_renderFromPicker()} -
+ _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 + return unless @props.value + label = @props.value.toString() + multipleAccounts = @props.accounts.length > 1 + hasAliases = @props.accounts[0]?.aliases.length > 0 + if multipleAccounts or hasAliases + {label}} - 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 + ) + {contact.toString()} + + _renderAccounts: (accounts) => + items = AccountStore.aliasesFor(accounts) alias } - itemContent={ (alias) -> alias } - onSelect={@_onChooseAlias.bind(@, account)} /> + items={items} + itemKey={(contact) -> contact.id} + itemContent={@_renderMenuItem} + onSelect={@_onChooseContact} /> _renderAccountSpan: (label) -> {label} - _onChooseAlias: (account, alias) => - @props.onChange(account.meUsingAlias(alias)) - @refs.dropdown.toggleDropdown() + render: => +
+
From:
+ {@_renderAccountSelector()} +
module.exports = AccountContactField diff --git a/internal_packages/composer/lib/composer-view.cjsx b/internal_packages/composer/lib/composer-view.cjsx index d920706d0..a8c381c4f 100644 --- a/internal_packages/composer/lib/composer-view.cjsx +++ b/internal_packages/composer/lib/composer-view.cjsx @@ -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 diff --git a/internal_packages/composer/lib/expanded-participants.cjsx b/internal_packages/composer/lib/expanded-participants.cjsx index 17d7fd6c5..d7bb75eb0 100644 --- a/internal_packages/composer/lib/expanded-participants.cjsx +++ b/internal_packages/composer/lib/expanded-participants.cjsx @@ -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 @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]} /> ) diff --git a/internal_packages/composer/spec/composer-view-spec.cjsx b/internal_packages/composer/spec/composer-view-spec.cjsx index d8fb654e2..cff089b98 100644 --- a/internal_packages/composer/spec/composer-view-spec.cjsx +++ b/internal_packages/composer/spec/composer-view-spec.cjsx @@ -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 {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 diff --git a/internal_packages/composer/stylesheets/composer.less b/internal_packages/composer/stylesheets/composer.less index f13206571..f6534483c 100644 --- a/internal_packages/composer/stylesheets/composer.less +++ b/internal_packages/composer/stylesheets/composer.less @@ -323,6 +323,12 @@ body.platform-win32 { .secondary-items { border-radius: 4px; } + .item { + .contact.is-alias { + font-style: italic; + float: right; + } + } } .participant { diff --git a/internal_packages/preferences/lib/tabs/preferences-general.cjsx b/internal_packages/preferences/lib/tabs/preferences-general.cjsx index 293b80b5c..1cb54285c 100644 --- a/internal_packages/preferences/lib/tabs/preferences-general.cjsx +++ b/internal_packages/preferences/lib/tabs/preferences-general.cjsx @@ -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} /> - + + 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) + + + + +module.exports = SendingSection diff --git a/src/flux/models/account.coffee b/src/flux/models/account.coffee index 0476b08cc..b54c3da09 100644 --- a/src/flux/models/account.coffee +++ b/src/flux/models/account.coffee @@ -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" diff --git a/src/flux/models/contact.coffee b/src/flux/models/contact.coffee index 7673ffcf7..daab0e9bd 100644 --- a/src/flux/models/contact.coffee +++ b/src/flux/models/contact.coffee @@ -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 diff --git a/src/flux/stores/account-store.coffee b/src/flux/stores/account-store.coffee index e4bc326f2..0b85abdae 100644 --- a/src/flux/stores/account-store.coffee +++ b/src/flux/stores/account-store.coffee @@ -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") diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee index 5299bafb2..e93da93f8 100644 --- a/src/flux/stores/draft-store.coffee +++ b/src/flux/stores/draft-store.coffee @@ -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)