mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 13:14:16 +08:00
Update the changelog
This commit is contained in:
parent
d769504d1c
commit
d6c336f070
2 changed files with 30 additions and 211 deletions
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,5 +1,35 @@
|
|||
# Mailspring Changelog
|
||||
|
||||
### 1.0.2 (10/6/2017)
|
||||
|
||||
Fixes:
|
||||
|
||||
- During authentication, you can now view a "Raw Log" of the IMAP and SMTP communication with your servers for easy debugging of connection issues.
|
||||
|
||||
- During authentication, Mailspring will warn you if you connect Gmail via IMAP.
|
||||
|
||||
- The "Install Theme...", "Install a Plugin Manually..." and "Create a Plugin..." menu items now work. Note that Nylas Mail / N1 themes require some modifications to work with Mailspring!
|
||||
|
||||
- On Windows and Linux, Mailspring can now make itself the default mail client.
|
||||
|
||||
- The contact sidebar in the app now works reliably and is rate-limited for free users (The Clearbit API is very expensive!)
|
||||
|
||||
- On Windows, Mailspring now displays emails with encoded subject lines (often containing emoji or foreign characters) correctly.
|
||||
|
||||
- On Windows, you can now resize and maximize the Mailspring window.
|
||||
|
||||
- Mailspring now skips folders it can't sync rather than stopping the entire account.
|
||||
|
||||
### 1.0.1 (10/4/2017)
|
||||
|
||||
Fixes:
|
||||
|
||||
- On Linux, Mailspring now syncs mail reliably thanks to fixed builds of curl and mailcore2.
|
||||
|
||||
- On Windows, the app's icon now includes all the required resolutions.
|
||||
|
||||
- Many other minor fixes and sync improvements.
|
||||
|
||||
### 1.0.0 (10/3/2017)
|
||||
|
||||
Features:
|
||||
|
|
|
@ -1,211 +0,0 @@
|
|||
import { ipcRenderer, shell } from 'electron';
|
||||
import { React, PropTypes, Actions } from 'mailspring-exports';
|
||||
import { RetinaImg } from 'mailspring-component-kit';
|
||||
|
||||
const clipboard = require('electron').clipboard;
|
||||
|
||||
export default class OAuthSignInPage extends React.Component {
|
||||
static displayName = 'OAuthSignInPage';
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Step 1: Open a webpage in the user's browser letting them login on
|
||||
* the native provider's website. We pass along a key and a redirect
|
||||
* url to a Nylas-owned server
|
||||
*/
|
||||
providerAuthPageUrl: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Step 2: Poll a Nylas server with this function looking for the key.
|
||||
* Once users complete the auth successfully, Nylas servers will get
|
||||
* the token and vend it back to us via this url. We need to poll
|
||||
* since we don't know how long it'll take users to log in on their
|
||||
* provider's website.
|
||||
*/
|
||||
tokenRequestPollFn: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Once we have the token, we can use that to retrieve the full
|
||||
* account credentials or establish a direct connection ourselves.
|
||||
* Some Nylas backends vend all account credentials along with the
|
||||
* token making this function unnecessary and a no-op. Mailspring
|
||||
* local sync needs to use the returned OAuth token to establish an
|
||||
* IMAP connection directly that may have its own set of failure
|
||||
* cases.
|
||||
*/
|
||||
accountFromTokenFn: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Called once we have successfully received the account data from
|
||||
* `accountFromTokenFn`
|
||||
*/
|
||||
onSuccess: PropTypes.func,
|
||||
|
||||
onTryAgain: PropTypes.func,
|
||||
iconName: PropTypes.string,
|
||||
sessionKey: PropTypes.string,
|
||||
serviceName: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
authStage: 'initial',
|
||||
showAlternative: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Show the "Sign in to ..." prompt for a moment before bouncing
|
||||
// to URL. (400msec animation + 200msec to read)
|
||||
this._pollTimer = null;
|
||||
this._startTimer = setTimeout(() => {
|
||||
shell.openExternal(this.props.providerAuthPageUrl);
|
||||
this.startPollingForResponse();
|
||||
}, 600);
|
||||
setTimeout(() => {
|
||||
this.setState({ showAlternative: true });
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._startTimer) clearTimeout(this._startTimer);
|
||||
if (this._pollTimer) clearTimeout(this._pollTimer);
|
||||
}
|
||||
|
||||
_handleError(err) {
|
||||
this.setState({ authStage: 'error', errorMessage: err.message });
|
||||
AppEnv.reportError(err);
|
||||
Actions.recordUserEvent('Email Account Auth Failed', {
|
||||
errorMessage: err.message,
|
||||
provider: 'gmail',
|
||||
});
|
||||
}
|
||||
|
||||
startPollingForResponse() {
|
||||
let delay = 1000;
|
||||
let onWindowFocused = null;
|
||||
let poll = null;
|
||||
this.setState({ authStage: 'polling' });
|
||||
|
||||
onWindowFocused = () => {
|
||||
delay = 1000;
|
||||
if (this._pollTimer) {
|
||||
clearTimeout(this._pollTimer);
|
||||
this._pollTimer = setTimeout(poll, delay);
|
||||
}
|
||||
};
|
||||
|
||||
poll = async () => {
|
||||
clearTimeout(this._pollTimer);
|
||||
try {
|
||||
const tokenData = await this.props.tokenRequestPollFn(this.props.sessionKey);
|
||||
ipcRenderer.removeListener('browser-window-focus', onWindowFocused);
|
||||
this.fetchAccountDataWithToken(tokenData);
|
||||
} catch (err) {
|
||||
if (err.statusCode === 404) {
|
||||
delay = Math.min(delay * 1.1, 3000);
|
||||
this._pollTimer = setTimeout(poll, delay);
|
||||
} else {
|
||||
ipcRenderer.removeListener('browser-window-focus', onWindowFocused);
|
||||
this._handleError(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ipcRenderer.on('browser-window-focus', onWindowFocused);
|
||||
this._pollTimer = setTimeout(poll, 3000);
|
||||
}
|
||||
|
||||
async fetchAccountDataWithToken(tokenData) {
|
||||
try {
|
||||
this.setState({ authStage: 'fetchingAccount' });
|
||||
const accountData = await this.props.accountFromTokenFn(tokenData);
|
||||
this.props.onSuccess(accountData);
|
||||
this.setState({ authStage: 'accountSuccess' });
|
||||
} catch (err) {
|
||||
this._handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
_renderHeader() {
|
||||
const authStage = this.state.authStage;
|
||||
if (authStage === 'initial' || authStage === 'polling') {
|
||||
return (
|
||||
<h2>
|
||||
Sign in with {this.props.serviceName} in<br />your browser.
|
||||
</h2>
|
||||
);
|
||||
} else if (authStage === 'fetchingAccount') {
|
||||
return <h2>Connecting to {this.props.serviceName}…</h2>;
|
||||
} else if (authStage === 'accountSuccess') {
|
||||
return (
|
||||
<div>
|
||||
<h2>Successfully connected to {this.props.serviceName}!</h2>
|
||||
<h3>Adding your account to Mailspring…</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Error
|
||||
return (
|
||||
<div>
|
||||
<h2>Sorry, we had trouble logging you in</h2>
|
||||
<div className="error-region">
|
||||
<p className="message error error-message">{this.state.errorMessage}</p>
|
||||
<p className="extra">
|
||||
Please <a onClick={this.props.onTryAgain}>try again</a>. If you continue to see this
|
||||
error contact support@getmailspring.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderAlternative() {
|
||||
let classnames = 'input hidden';
|
||||
if (this.state.authStage === 'polling' && this.state.showAlternative) {
|
||||
classnames += ' fadein';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alternative-auth">
|
||||
<div className={classnames}>
|
||||
<div style={{ marginTop: 40 }}>
|
||||
Page didn't open? Paste this URL into your browser:
|
||||
</div>
|
||||
<input
|
||||
type="url"
|
||||
className="url-copy-target"
|
||||
value={this.props.providerAuthPageUrl}
|
||||
readOnly
|
||||
/>
|
||||
<div
|
||||
className="copy-to-clipboard"
|
||||
onClick={() => clipboard.writeText(this.props.providerAuthPageUrl)}
|
||||
onMouseDown={() => this.setState({ pressed: true })}
|
||||
onMouseUp={() => this.setState({ pressed: false })}
|
||||
>
|
||||
<RetinaImg name="icon-copytoclipboard.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`page account-setup ${this.props.serviceName.toLowerCase()}`}>
|
||||
<div className="logo-container">
|
||||
<RetinaImg
|
||||
name={this.props.iconName}
|
||||
mode={RetinaImg.Mode.ContentPreserve}
|
||||
className="logo"
|
||||
/>
|
||||
</div>
|
||||
{this._renderHeader()}
|
||||
{this._renderAlternative()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue