2016-05-07 07:06:16 +08:00
|
|
|
/* eslint global-require: 0 */
|
2015-12-10 04:35:40 +08:00
|
|
|
import React, {Component, PropTypes} from 'react';
|
2016-01-08 10:44:40 +08:00
|
|
|
import {EditableList, NewsletterSignup} from 'nylas-component-kit';
|
2016-03-09 08:06:04 +08:00
|
|
|
import {RegExpUtils, Account} from 'nylas-exports';
|
2015-12-10 04:35:40 +08:00
|
|
|
|
|
|
|
class PreferencesAccountDetails extends Component {
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
account: PropTypes.object,
|
|
|
|
onAccountUpdated: PropTypes.func.isRequired,
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-10 04:35:40 +08:00
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2016-05-06 06:24:51 +08:00
|
|
|
this.state = {account: props.account.clone()};
|
2015-12-10 04:35:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) {
|
2016-05-06 06:24:51 +08:00
|
|
|
this.setState({account: nextProps.account.clone()});
|
2015-12-10 04:35:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this._saveChanges();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helpers
|
|
|
|
|
2015-12-11 09:59:16 +08:00
|
|
|
/**
|
2015-12-12 02:01:13 +08:00
|
|
|
* @private Will transform any user input into alias format.
|
2015-12-11 09:59:16 +08:00
|
|
|
* It will ignore any text after an email, if one is entered.
|
|
|
|
* If no email is entered, it will use the account's email.
|
|
|
|
* It will treat the text before the email as the name for the alias.
|
|
|
|
* If no name is entered, it will use the account's name value.
|
2015-12-12 02:01:13 +08:00
|
|
|
* @param {string} str - The string the user entered on the alias input
|
|
|
|
* @param {object} [account=this.props.account] - The account object
|
2015-12-11 09:59:16 +08:00
|
|
|
*/
|
2015-12-10 04:35:40 +08:00
|
|
|
_makeAlias(str, account = this.props.account) {
|
|
|
|
const emailRegex = RegExpUtils.emailRegex();
|
|
|
|
const match = emailRegex.exec(str);
|
|
|
|
if (!match) {
|
2016-02-02 04:45:52 +08:00
|
|
|
return `${str || account.name} <${account.emailAddress}>`;
|
2015-12-10 04:35:40 +08:00
|
|
|
}
|
|
|
|
const email = match[0];
|
|
|
|
let name = str.slice(0, Math.max(0, match.index - 1));
|
|
|
|
if (!name) {
|
|
|
|
name = account.name || 'No name provided';
|
|
|
|
}
|
2015-12-15 06:29:45 +08:00
|
|
|
name = name.trim();
|
2015-12-10 04:35:40 +08:00
|
|
|
// TODO Sanitize the name string
|
|
|
|
return `${name} <${email}>`;
|
|
|
|
}
|
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_saveChanges = () => {
|
2016-02-17 10:03:22 +08:00
|
|
|
this.props.onAccountUpdated(this.props.account, this.state.account);
|
|
|
|
};
|
|
|
|
|
2016-05-07 07:06:16 +08:00
|
|
|
_setState = (updates, callback = () => {}) => {
|
2016-05-14 05:16:54 +08:00
|
|
|
const account = Object.assign(this.state.account.clone(), updates);
|
|
|
|
this.setState({account}, callback);
|
2016-02-17 10:03:22 +08:00
|
|
|
};
|
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_setStateAndSave = (updates) => {
|
|
|
|
this._setState(updates, () => {
|
2016-02-17 10:03:22 +08:00
|
|
|
this._saveChanges();
|
|
|
|
});
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-10 04:35:40 +08:00
|
|
|
|
|
|
|
|
|
|
|
// Handlers
|
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_onAccountLabelUpdated = (event) => {
|
2016-02-17 10:03:22 +08:00
|
|
|
this._setState({label: event.target.value});
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-10 04:35:40 +08:00
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_onAccountAliasCreated = (newAlias) => {
|
2015-12-10 04:35:40 +08:00
|
|
|
const coercedAlias = this._makeAlias(newAlias);
|
2016-02-27 03:00:06 +08:00
|
|
|
const aliases = this.state.account.aliases.concat([coercedAlias]);
|
2016-02-17 10:03:22 +08:00
|
|
|
this._setStateAndSave({aliases})
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-10 04:35:40 +08:00
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_onAccountAliasUpdated = (newAlias, alias, idx) => {
|
2015-12-10 04:35:40 +08:00
|
|
|
const coercedAlias = this._makeAlias(newAlias);
|
2016-02-27 03:00:06 +08:00
|
|
|
const aliases = this.state.account.aliases.slice();
|
|
|
|
let defaultAlias = this.state.account.defaultAlias;
|
2015-12-15 06:34:13 +08:00
|
|
|
if (defaultAlias === alias) {
|
|
|
|
defaultAlias = coercedAlias;
|
|
|
|
}
|
2015-12-10 04:35:40 +08:00
|
|
|
aliases[idx] = coercedAlias;
|
2016-02-17 10:03:22 +08:00
|
|
|
this._setStateAndSave({aliases, defaultAlias});
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-10 04:35:40 +08:00
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_onAccountAliasRemoved = (alias, idx) => {
|
2016-02-27 03:00:06 +08:00
|
|
|
const aliases = this.state.account.aliases.slice();
|
|
|
|
let defaultAlias = this.state.account.defaultAlias;
|
2015-12-15 06:34:13 +08:00
|
|
|
if (defaultAlias === alias) {
|
|
|
|
defaultAlias = null;
|
|
|
|
}
|
2015-12-10 04:35:40 +08:00
|
|
|
aliases.splice(idx, 1);
|
2016-02-17 10:03:22 +08:00
|
|
|
this._setStateAndSave({aliases, defaultAlias});
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-10 04:35:40 +08:00
|
|
|
|
2016-05-06 13:30:34 +08:00
|
|
|
_onDefaultAliasSelected = (event) => {
|
2015-12-15 06:34:13 +08:00
|
|
|
const defaultAlias = event.target.value === 'None' ? null : event.target.value;
|
2016-02-17 10:03:22 +08:00
|
|
|
this._setStateAndSave({defaultAlias});
|
2016-02-02 12:06:29 +08:00
|
|
|
};
|
2015-12-15 06:34:13 +08:00
|
|
|
|
2016-05-14 05:16:54 +08:00
|
|
|
_onReconnect = () => {
|
2016-03-09 08:06:04 +08:00
|
|
|
const ipc = require('electron').ipcRenderer;
|
2017-02-23 05:49:17 +08:00
|
|
|
ipc.send('command', 'application:add-account', {existingAccount: this.state.account, source: 'Reconnect from preferences'});
|
2016-03-09 08:06:04 +08:00
|
|
|
}
|
2016-05-14 05:16:54 +08:00
|
|
|
|
|
|
|
_onContactSupport = () => {
|
2016-03-09 08:06:04 +08:00
|
|
|
const {shell} = require("electron");
|
|
|
|
shell.openExternal("https://support.nylas.com/hc/en-us/requests/new");
|
|
|
|
}
|
2015-12-15 06:34:13 +08:00
|
|
|
|
|
|
|
// Renderers
|
|
|
|
|
|
|
|
_renderDefaultAliasSelector(account) {
|
|
|
|
const aliases = account.aliases;
|
|
|
|
const defaultAlias = account.defaultAlias || 'None';
|
|
|
|
if (aliases.length > 0) {
|
|
|
|
return (
|
|
|
|
<div className="default-alias-selector">
|
2016-04-16 07:05:54 +08:00
|
|
|
<div>Default for new messages:</div>
|
2015-12-15 06:34:13 +08:00
|
|
|
<select value={defaultAlias} onChange={this._onDefaultAliasSelected}>
|
2016-04-16 07:05:54 +08:00
|
|
|
<option value="None">{`${account.name} <${account.emailAddress}>`}</option>
|
2016-05-06 13:30:34 +08:00
|
|
|
{aliases.map((alias, idx) => <option key={`alias-${idx}`} value={alias}>{alias}</option>)}
|
2015-12-15 06:34:13 +08:00
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2016-05-07 07:06:16 +08:00
|
|
|
return null;
|
2015-12-15 06:34:13 +08:00
|
|
|
}
|
|
|
|
|
2016-03-09 08:06:04 +08:00
|
|
|
|
|
|
|
_renderErrorDetail(message, buttonText, buttonAction) {
|
|
|
|
return (<div className="account-error-detail">
|
|
|
|
<div className="message">{message}</div>
|
|
|
|
<a className="action" onClick={buttonAction}>{buttonText}</a>
|
|
|
|
</div>)
|
|
|
|
}
|
2016-05-07 07:06:16 +08:00
|
|
|
|
2016-03-09 08:06:04 +08:00
|
|
|
_renderSyncErrorDetails() {
|
|
|
|
const {account} = this.state;
|
2016-03-19 01:41:08 +08:00
|
|
|
if (account.hasSyncStateError()) {
|
2016-03-09 08:06:04 +08:00
|
|
|
switch (account.syncState) {
|
2016-12-02 01:04:37 +08:00
|
|
|
case Account.N1_Cloud_AUTH_FAILED:
|
|
|
|
return this._renderErrorDetail(
|
2017-01-12 09:41:07 +08:00
|
|
|
`Nylas Mail can no longer authenticate N1 Cloud Services with
|
2016-12-02 01:04:37 +08:00
|
|
|
${account.emailAddress}. The password or authentication may
|
|
|
|
have changed.`,
|
|
|
|
"Reconnect",
|
|
|
|
this._onReconnect);
|
2016-05-07 07:06:16 +08:00
|
|
|
case Account.SYNC_STATE_AUTH_FAILED:
|
|
|
|
return this._renderErrorDetail(
|
2017-01-12 09:41:07 +08:00
|
|
|
`Nylas Mail can no longer authenticate with ${account.emailAddress}. The password or
|
2016-05-07 07:06:16 +08:00
|
|
|
authentication may have changed.`,
|
|
|
|
"Reconnect",
|
2016-05-14 05:16:54 +08:00
|
|
|
this._onReconnect);
|
2016-05-07 07:06:16 +08:00
|
|
|
default:
|
|
|
|
return this._renderErrorDetail(
|
|
|
|
`Nylas encountered an error while syncing mail for ${account.emailAddress}. Contact Nylas support for details.`,
|
|
|
|
"Contact support",
|
2016-05-14 05:16:54 +08:00
|
|
|
this._onContactSupport);
|
2016-03-09 08:06:04 +08:00
|
|
|
}
|
|
|
|
}
|
2016-05-07 07:06:16 +08:00
|
|
|
return null;
|
2016-03-09 08:06:04 +08:00
|
|
|
}
|
|
|
|
|
2015-12-10 04:35:40 +08:00
|
|
|
render() {
|
2016-02-17 10:03:22 +08:00
|
|
|
const {account} = this.state;
|
2015-12-10 04:35:40 +08:00
|
|
|
const aliasPlaceholder = this._makeAlias(
|
|
|
|
`alias@${account.emailAddress.split('@')[1]}`
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="account-details">
|
2016-05-14 05:16:54 +08:00
|
|
|
{this._renderSyncErrorDetails()}
|
2015-12-10 04:35:40 +08:00
|
|
|
<h3>Account Label</h3>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
value={account.label}
|
|
|
|
onBlur={this._saveChanges}
|
2016-05-07 07:06:16 +08:00
|
|
|
onChange={this._onAccountLabelUpdated}
|
|
|
|
/>
|
2015-12-16 03:56:31 +08:00
|
|
|
|
2016-05-14 05:16:54 +08:00
|
|
|
<h3>Account Settings</h3>
|
|
|
|
|
|
|
|
<div className="btn" onClick={this._onReconnect}>
|
2017-01-12 05:54:30 +08:00
|
|
|
{account.provider === 'imap' ? 'Update Connection Settings...' : 'Re-authenticate...'}
|
2016-05-14 05:16:54 +08:00
|
|
|
</div>
|
|
|
|
|
2015-12-10 04:35:40 +08:00
|
|
|
<h3>Aliases</h3>
|
2015-12-16 03:56:31 +08:00
|
|
|
|
|
|
|
<div className="platform-note">
|
|
|
|
You may need to configure aliases with your
|
|
|
|
mail provider (Outlook, Gmail) before using them.
|
|
|
|
</div>
|
|
|
|
|
2015-12-10 04:35:40 +08:00
|
|
|
<EditableList
|
|
|
|
showEditIcon
|
feat(mail-rules): Per-account mail rules filter incoming, existing mail
Summary:
Originally, this was going to be a totally independent package, but
I wasn't able to isolate the functionality and get it tied in to
the delta-stream consumption. Here's how it currently works:
- The preferences package has a new tab which allows you to edit
mail filters. Filters are saved in a new core store, and a new
stock component (ScenarioEditor) renders the editor. The editor
takes a set of templates that define a value space, and outputs
a valid set of values.
- A new MailFilterProcessor takes messages and creates tasks to
apply the actions from the MailFiltersStore.
- The worker-sync package now uses the MailFilterProcessor to
apply filters /before/ it calls didPassivelyReceiveNewModels,
so filtrs are applied before any notifications are created.
- A new task, ReprocessMailFiltersTask allows you to run filters
on all of your existing mail. It leverages the existing TaskQueue
architecture to: a) resume where it left off if you quit midway,
b) be queryable (for status) from all windows and c) cancelable.
The TaskQueue is a bit strange because it runs performLocal and
performRemote very differently, and I had to use `performRemote`.
(todo refactor soon.)
This diff also changes the EditableList a bit to behave like a
controlled component and render focused / unfocused states.
Test Plan: Run tests, only for actual filter processing atm.
Reviewers: juan, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2379
2015-12-23 15:19:32 +08:00
|
|
|
items={account.aliases}
|
2015-12-10 04:35:40 +08:00
|
|
|
createInputProps={{placeholder: aliasPlaceholder}}
|
|
|
|
onItemCreated={this._onAccountAliasCreated}
|
|
|
|
onItemEdited={this._onAccountAliasUpdated}
|
2016-05-07 07:06:16 +08:00
|
|
|
onDeleteItem={this._onAccountAliasRemoved}
|
|
|
|
/>
|
feat(mail-rules): Per-account mail rules filter incoming, existing mail
Summary:
Originally, this was going to be a totally independent package, but
I wasn't able to isolate the functionality and get it tied in to
the delta-stream consumption. Here's how it currently works:
- The preferences package has a new tab which allows you to edit
mail filters. Filters are saved in a new core store, and a new
stock component (ScenarioEditor) renders the editor. The editor
takes a set of templates that define a value space, and outputs
a valid set of values.
- A new MailFilterProcessor takes messages and creates tasks to
apply the actions from the MailFiltersStore.
- The worker-sync package now uses the MailFilterProcessor to
apply filters /before/ it calls didPassivelyReceiveNewModels,
so filtrs are applied before any notifications are created.
- A new task, ReprocessMailFiltersTask allows you to run filters
on all of your existing mail. It leverages the existing TaskQueue
architecture to: a) resume where it left off if you quit midway,
b) be queryable (for status) from all windows and c) cancelable.
The TaskQueue is a bit strange because it runs performLocal and
performRemote very differently, and I had to use `performRemote`.
(todo refactor soon.)
This diff also changes the EditableList a bit to behave like a
controlled component and render focused / unfocused states.
Test Plan: Run tests, only for actual filter processing atm.
Reviewers: juan, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2379
2015-12-23 15:19:32 +08:00
|
|
|
|
2015-12-15 06:34:13 +08:00
|
|
|
{this._renderDefaultAliasSelector(account)}
|
2016-01-08 10:44:40 +08:00
|
|
|
|
|
|
|
<div className="newsletter">
|
|
|
|
<NewsletterSignup emailAddress={account.emailAddress} name={account.name} />
|
|
|
|
</div>
|
2016-03-09 08:06:04 +08:00
|
|
|
|
2015-12-10 04:35:40 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export default PreferencesAccountDetails;
|