mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-12 04:25:31 +08:00
bd361c8abb
Summary: Refactored signature preferences page to allow more signatures than the previous 1-1 mapping for signatures and accounts. Created a multi select dropdown of the accounts for which a certain signature is set as default for. Added a button into the draft header From field to toggle between saved signatures. refactor(signatures): Add basic add/remove capabilities to static refactor(signatures): Hooked up signature actions and signature store for basic functionality fix(signatures): Cleaned up signature store and started on multiselectdropdown fix(signatures): Add multi signature toggle select to multiselect dropdown build(signatures): Built framework for multiselect dropdown build(signatures): Toggle button functionality for dropdown build(signatures): Build multi select from components and add debounce refactor(signatures): Move signature actions and signature store into flux fix(signatures): Styled composer signatures button/dropdown and fixed preferences checkmarks build(signatures): Finish main functionality, about to refactor composer signature button into injected component fix(signatures): Changed position styles fix(signatures): Fixed background color for dropdown button when blurred build(signatures): Began to write tests for signatures store, preferences and dropdown Test Plan: Wrote tests for preferences signatures, signature store, signature composer dropdown and refactored tests for signature composer extension. For signature composer extension I removed applyTransformsToDraft and unapplyTransformsToDraft and tested by sending emails with signatures to different providers to make sure the <signature> tag caused problems. Reviewers: bengotow, juan Reviewed By: juan Differential Revision: https://phab.nylas.com/D3073
302 lines
8.2 KiB
JavaScript
302 lines
8.2 KiB
JavaScript
import _ from 'underscore';
|
|
import React from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import AccountContactField from './account-contact-field';
|
|
import {Utils, DraftHelpers, Actions, AccountStore} from 'nylas-exports';
|
|
import {
|
|
InjectedComponent,
|
|
KeyCommandsRegion,
|
|
ParticipantsTextField,
|
|
ListensToFluxStore,
|
|
} from 'nylas-component-kit';
|
|
|
|
import CollapsedParticipants from './collapsed-participants';
|
|
import ComposerHeaderActions from './composer-header-actions';
|
|
import SubjectTextField from './subject-text-field';
|
|
|
|
import Fields from './fields';
|
|
|
|
const ScopedFromField = ListensToFluxStore(AccountContactField, {
|
|
stores: [AccountStore],
|
|
getStateFromStores: (props) => {
|
|
const savedOrReplyToThread = !!props.draft.threadId;
|
|
if (savedOrReplyToThread) {
|
|
return {accounts: [AccountStore.accountForId(props.draft.accountId)]};
|
|
}
|
|
return {accounts: AccountStore.accounts()}
|
|
},
|
|
});
|
|
|
|
export default class ComposerHeader extends React.Component {
|
|
static displayName = "ComposerHeader";
|
|
|
|
static propTypes = {
|
|
draft: React.PropTypes.object.isRequired,
|
|
session: React.PropTypes.object.isRequired,
|
|
initiallyFocused: React.PropTypes.bool,
|
|
}
|
|
|
|
static contextTypes = {
|
|
parentTabGroup: React.PropTypes.object,
|
|
}
|
|
|
|
constructor(props = {}) {
|
|
super(props)
|
|
this.state = this._initialStateForDraft(this.props.draft, props);
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
if (this.props.session !== nextProps.session) {
|
|
this.setState(this._initialStateForDraft(nextProps.draft, nextProps));
|
|
} else {
|
|
this._ensureFilledFieldsEnabled(nextProps.draft);
|
|
}
|
|
}
|
|
|
|
showAndFocusField = (fieldName) => {
|
|
const enabledFields = _.uniq([].concat(this.state.enabledFields, [fieldName]));
|
|
const participantsFocused = this.state.participantsFocused || Fields.ParticipantFields.includes(fieldName);
|
|
|
|
Utils.waitFor(() => this.refs[fieldName]).then(() =>
|
|
this.refs[fieldName].focus()
|
|
).catch(() => {
|
|
})
|
|
|
|
this.setState({enabledFields, participantsFocused});
|
|
}
|
|
|
|
hideField = (fieldName) => {
|
|
if (ReactDOM.findDOMNode(this.refs[fieldName]).contains(document.activeElement)) {
|
|
this.context.parentTabGroup.shiftFocus(-1)
|
|
}
|
|
|
|
const enabledFields = _.without(this.state.enabledFields, fieldName)
|
|
this.setState({enabledFields})
|
|
}
|
|
|
|
_ensureFilledFieldsEnabled(draft) {
|
|
let enabledFields = this.state.enabledFields;
|
|
if (!_.isEmpty(draft.cc)) {
|
|
enabledFields = enabledFields.concat([Fields.Cc]);
|
|
}
|
|
if (!_.isEmpty(draft.bcc)) {
|
|
enabledFields = enabledFields.concat([Fields.Bcc]);
|
|
}
|
|
if (enabledFields !== this.state.enabledFields) {
|
|
this.setState({enabledFields});
|
|
}
|
|
}
|
|
|
|
_initialStateForDraft(draft, props) {
|
|
const enabledFields = [Fields.To];
|
|
if (!_.isEmpty(draft.cc)) {
|
|
enabledFields.push(Fields.Cc);
|
|
}
|
|
if (!_.isEmpty(draft.bcc)) {
|
|
enabledFields.push(Fields.Bcc);
|
|
}
|
|
enabledFields.push(Fields.From);
|
|
if (this._shouldEnableSubject()) {
|
|
enabledFields.push(Fields.Subject);
|
|
}
|
|
|
|
return {
|
|
enabledFields,
|
|
participantsFocused: props.initiallyFocused,
|
|
};
|
|
}
|
|
|
|
_shouldEnableSubject = () => {
|
|
if (_.isEmpty(this.props.draft.subject)) {
|
|
return true;
|
|
}
|
|
if (DraftHelpers.isForwardedMessage(this.props.draft)) {
|
|
return true;
|
|
}
|
|
if (this.props.draft.replyToMessageId) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
_onChangeParticipants = (changes) => {
|
|
this.props.session.changes.add(changes);
|
|
Actions.draftParticipantsChanged(this.props.draft.clientId, changes);
|
|
}
|
|
|
|
_onSubjectChange = (value) => {
|
|
this.props.session.changes.add({subject: value});
|
|
}
|
|
|
|
_onFocusInParticipants = () => {
|
|
const fieldName = this.state.participantsLastActiveField || Fields.To;
|
|
Utils.waitFor(() =>
|
|
this.refs[fieldName]
|
|
).then(() =>
|
|
this.refs[fieldName].focus()
|
|
).catch(() => {
|
|
});
|
|
|
|
this.setState({
|
|
participantsFocused: true,
|
|
participantsLastActiveField: null,
|
|
});
|
|
}
|
|
|
|
_onFocusOutParticipants = (lastFocusedEl) => {
|
|
const active = Fields.ParticipantFields.find((fieldName) => {
|
|
return this.refs[fieldName] ? ReactDOM.findDOMNode(this.refs[fieldName]).contains(lastFocusedEl) : false
|
|
}
|
|
);
|
|
this.setState({
|
|
participantsFocused: false,
|
|
participantsLastActiveField: active,
|
|
});
|
|
}
|
|
|
|
_onDragCollapsedParticipants({isDropping}) {
|
|
if (isDropping) {
|
|
this.setState({
|
|
participantsFocused: true,
|
|
enabledFields: [...Fields.ParticipantFields, Fields.From, Fields.Subject],
|
|
})
|
|
}
|
|
}
|
|
|
|
_renderParticipants = () => {
|
|
let content = null;
|
|
if (this.state.participantsFocused) {
|
|
content = this._renderFields();
|
|
} else {
|
|
content = (
|
|
<CollapsedParticipants
|
|
to={this.props.draft.to}
|
|
cc={this.props.draft.cc}
|
|
bcc={this.props.draft.bcc}
|
|
onDragChange={::this._onDragCollapsedParticipants}
|
|
/>
|
|
)
|
|
}
|
|
|
|
// When the participants field collapses, we store the field that was last
|
|
// focused onto our state, so that we can restore focus to it when the fields
|
|
// are expanded again.
|
|
return (
|
|
<KeyCommandsRegion
|
|
tabIndex={-1}
|
|
ref="participantsContainer"
|
|
className="expanded-participants"
|
|
onFocusIn={this._onFocusInParticipants}
|
|
onFocusOut={this._onFocusOutParticipants}
|
|
>
|
|
{content}
|
|
</KeyCommandsRegion>
|
|
);
|
|
}
|
|
|
|
_renderSubject = () => {
|
|
if (!this.state.enabledFields.includes(Fields.Subject)) {
|
|
return false;
|
|
}
|
|
const {draft, session} = this.props
|
|
return (
|
|
<InjectedComponent
|
|
ref={Fields.Subject}
|
|
key="subject-wrap"
|
|
matching={{role: 'Composer:SubjectTextField'}}
|
|
exposedProps={{
|
|
draft,
|
|
session,
|
|
value: draft.subject,
|
|
draftClientId: draft.clientId,
|
|
onSubjectChange: this._onSubjectChange,
|
|
}}
|
|
requiredMethods={['focus']}
|
|
fallback={SubjectTextField}
|
|
/>
|
|
)
|
|
}
|
|
|
|
_renderFields = () => {
|
|
const {to, cc, bcc, from} = this.props.draft;
|
|
|
|
// Note: We need to physically add and remove these elements, not just hide them.
|
|
// If they're hidden, shift-tab between fields breaks.
|
|
const fields = [];
|
|
|
|
fields.push(
|
|
<ParticipantsTextField
|
|
ref={Fields.To}
|
|
key="to"
|
|
field="to"
|
|
change={this._onChangeParticipants}
|
|
className="composer-participant-field to-field"
|
|
participants={{to, cc, bcc}}
|
|
draft={this.props.draft}
|
|
session={this.props.session}
|
|
/>
|
|
)
|
|
|
|
if (this.state.enabledFields.includes(Fields.Cc)) {
|
|
fields.push(
|
|
<ParticipantsTextField
|
|
ref={Fields.Cc}
|
|
key="cc"
|
|
field="cc"
|
|
change={this._onChangeParticipants}
|
|
onEmptied={() => this.hideField(Fields.Cc)}
|
|
className="composer-participant-field cc-field"
|
|
participants={{to, cc, bcc}}
|
|
draft={this.props.draft}
|
|
session={this.props.session}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (this.state.enabledFields.includes(Fields.Bcc)) {
|
|
fields.push(
|
|
<ParticipantsTextField
|
|
ref={Fields.Bcc}
|
|
key="bcc"
|
|
field="bcc"
|
|
change={this._onChangeParticipants}
|
|
onEmptied={() => this.hideField(Fields.Bcc)}
|
|
className="composer-participant-field bcc-field"
|
|
participants={{to, cc, bcc}}
|
|
draft={this.props.draft}
|
|
session={this.props.session}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (this.state.enabledFields.includes(Fields.From)) {
|
|
fields.push(
|
|
<ScopedFromField
|
|
key="from"
|
|
ref={Fields.From}
|
|
draft={this.props.draft}
|
|
session={this.props.session}
|
|
onChange={this._onChangeParticipants}
|
|
value={from[0]}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return fields;
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="composer-header">
|
|
<ComposerHeaderActions
|
|
draftClientId={this.props.draft.clientId}
|
|
enabledFields={this.state.enabledFields}
|
|
participantsFocused={this.state.participantsFocused}
|
|
onShowAndFocusField={this.showAndFocusField}
|
|
/>
|
|
{this._renderParticipants()}
|
|
{this._renderSubject()}
|
|
</div>
|
|
)
|
|
}
|
|
}
|