mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-03 19:43:04 +08:00
Destroy drafts using ids not headerMessageIds
This commit is contained in:
parent
ad8b538763
commit
f051b52e8c
16 changed files with 94 additions and 62 deletions
|
@ -270,7 +270,7 @@ class ComposerEditor extends Component {
|
|||
shouldAcceptDrop={this._shouldAcceptDrop}
|
||||
>
|
||||
<Contenteditable
|
||||
ref={(cm) => { this._contenteditableComponent = cm; }}
|
||||
ref={(cm) => { if (cm) { this._contenteditableComponent = cm; } }}
|
||||
value={this.props.body}
|
||||
onChange={this.props.onBodyChanged}
|
||||
onFilePaste={this.props.onFilePaste}
|
||||
|
|
|
@ -214,7 +214,7 @@ export default class ComposerHeader extends React.Component {
|
|||
return (
|
||||
<KeyCommandsRegion
|
||||
tabIndex={-1}
|
||||
ref={(el) => { this._els.participantsContainer = el; }}
|
||||
ref={(el) => { if (el) { this._els.participantsContainer = el; } }}
|
||||
className="expanded-participants"
|
||||
onFocusIn={this._onFocusInParticipants}
|
||||
onFocusOut={this._onFocusOutParticipants}
|
||||
|
@ -232,12 +232,12 @@ export default class ComposerHeader extends React.Component {
|
|||
return (
|
||||
<KeyCommandsRegion
|
||||
tabIndex={-1}
|
||||
ref={(el) => { this._els.subjectContainer = el; }}
|
||||
ref={(el) => { if (el) { this._els.subjectContainer = el; } }}
|
||||
onFocusIn={this._onFocusInSubject}
|
||||
onFocusOut={this._onFocusOutSubject}
|
||||
>
|
||||
<InjectedComponent
|
||||
ref={(el) => { this._els[Fields.Subject] = el; }}
|
||||
ref={(el) => { if (el) { this._els[Fields.Subject] = el; } }}
|
||||
key="subject-wrap"
|
||||
matching={{role: 'Composer:SubjectTextField'}}
|
||||
exposedProps={{
|
||||
|
@ -264,7 +264,7 @@ export default class ComposerHeader extends React.Component {
|
|||
|
||||
fields.push(
|
||||
<ParticipantsTextField
|
||||
ref={(el) => { this._els[Fields.To] = el; }}
|
||||
ref={(el) => { if (el) { this._els[Fields.To] = el; } }}
|
||||
key="to"
|
||||
field="to"
|
||||
change={this._onChangeParticipants}
|
||||
|
@ -278,7 +278,7 @@ export default class ComposerHeader extends React.Component {
|
|||
if (this.state.enabledFields.includes(Fields.Cc)) {
|
||||
fields.push(
|
||||
<ParticipantsTextField
|
||||
ref={(el) => { this._els[Fields.Cc] = el; }}
|
||||
ref={(el) => { if (el) { this._els[Fields.Cc] = el; } }}
|
||||
key="cc"
|
||||
field="cc"
|
||||
change={this._onChangeParticipants}
|
||||
|
@ -294,7 +294,7 @@ export default class ComposerHeader extends React.Component {
|
|||
if (this.state.enabledFields.includes(Fields.Bcc)) {
|
||||
fields.push(
|
||||
<ParticipantsTextField
|
||||
ref={(el) => { this._els[Fields.Bcc] = el; }}
|
||||
ref={(el) => { if (el) { this._els[Fields.Bcc] = el; } }}
|
||||
key="bcc"
|
||||
field="bcc"
|
||||
change={this._onChangeParticipants}
|
||||
|
@ -311,7 +311,7 @@ export default class ComposerHeader extends React.Component {
|
|||
fields.push(
|
||||
<ScopedFromField
|
||||
key="from"
|
||||
ref={(el) => { this._els[Fields.From] = el; }}
|
||||
ref={(el) => { if (el) { this._els[Fields.From] = el; } }}
|
||||
value={from[0]}
|
||||
draft={this.props.draft}
|
||||
session={this.props.session}
|
||||
|
|
|
@ -148,12 +148,15 @@ export default class ComposerView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_setSREl = (el) => {
|
||||
this._els.scrollregion = el;
|
||||
}
|
||||
_renderContentScrollRegion() {
|
||||
if (NylasEnv.isComposerWindow()) {
|
||||
return (
|
||||
<ScrollRegion
|
||||
className="compose-body-scroll"
|
||||
ref={(el) => { this._els.scrollregion = el; }}
|
||||
ref={(el) => { if (el) { this._els.scrollregion = el; } }}
|
||||
>
|
||||
{this._renderContent()}
|
||||
</ScrollRegion>
|
||||
|
@ -172,7 +175,7 @@ export default class ComposerView extends React.Component {
|
|||
return (
|
||||
<div className="composer-centered">
|
||||
<ComposerHeader
|
||||
ref={(el) => { this._els.header = el; }}
|
||||
ref={(el) => { if (el) { this._els.header = el; } }}
|
||||
draft={this.props.draft}
|
||||
session={this.props.session}
|
||||
initiallyFocused={this.props.draft.to.length === 0}
|
||||
|
@ -180,7 +183,7 @@ export default class ComposerView extends React.Component {
|
|||
/>
|
||||
<div
|
||||
className="compose-body"
|
||||
ref={(el) => { this._els.composeBody = el; }}
|
||||
ref={(el) => { if (el) { this._els.composeBody = el; } }}
|
||||
onMouseUp={this._onMouseUpComposerBody}
|
||||
onMouseDown={this._onMouseDownComposerBody}
|
||||
>
|
||||
|
@ -197,7 +200,10 @@ export default class ComposerView extends React.Component {
|
|||
session: this.props.session,
|
||||
}
|
||||
return (
|
||||
<div ref={(el) => { this._els.composerBodyWrap = el; }} className="composer-body-wrap">
|
||||
<div
|
||||
ref={(el) => { if (el) { this._els.composerBodyWrap = el; } }}
|
||||
className="composer-body-wrap"
|
||||
>
|
||||
<OverlaidComponents exposedProps={exposedProps}>
|
||||
{this._renderEditor()}
|
||||
</OverlaidComponents>
|
||||
|
@ -221,7 +227,7 @@ export default class ComposerView extends React.Component {
|
|||
|
||||
return (
|
||||
<InjectedComponent
|
||||
ref={(el) => { this._els[Fields.Body] = el; }}
|
||||
ref={(el) => { if (el) { this._els[Fields.Body] = el; } }}
|
||||
className="body-field"
|
||||
matching={{role: "Composer:Editor"}}
|
||||
fallback={ComposerEditor}
|
||||
|
@ -390,7 +396,7 @@ export default class ComposerView extends React.Component {
|
|||
|
||||
|
||||
<InjectedComponent
|
||||
ref={(el) => { this._els.sendActionButton = el; }}
|
||||
ref={(el) => { if (el) { this._els.sendActionButton = el; } }}
|
||||
tabIndex={-1}
|
||||
style={{order: -100}}
|
||||
matching={{role: "Composer:SendActionButton"}}
|
||||
|
@ -564,7 +570,7 @@ export default class ComposerView extends React.Component {
|
|||
|
||||
_onDestroyDraft = () => {
|
||||
const {draft} = this.props;
|
||||
Actions.destroyDraft(draft.accountId, draft.headerMessageId);
|
||||
Actions.destroyDraft(draft);
|
||||
}
|
||||
|
||||
_onSelectAttachment = () => {
|
||||
|
@ -579,7 +585,7 @@ export default class ComposerView extends React.Component {
|
|||
<KeyCommandsRegion
|
||||
localHandlers={this._keymapHandlers()}
|
||||
className={"message-item-white-wrap composer-outer-wrap"}
|
||||
ref={(el) => { this._els.composerWrap = el; }}
|
||||
ref={(el) => { if (el) { this._els.composerWrap = el; } }}
|
||||
tabIndex="-1"
|
||||
>
|
||||
<TabGroupRegion className="composer-inner-wrap">
|
||||
|
|
|
@ -47,6 +47,6 @@ class DraftList extends React.Component
|
|||
|
||||
_onRemoveFromView: =>
|
||||
drafts = DraftListStore.dataSource().selection.items()
|
||||
Actions.destroyDraft(draft.accountId, draft.headerMessageId) for draft in drafts
|
||||
Actions.destroyDraft(draft) for draft in drafts
|
||||
|
||||
module.exports = DraftList
|
||||
|
|
|
@ -19,7 +19,7 @@ class DraftDeleteButton extends React.Component
|
|||
|
||||
_destroySelected: =>
|
||||
for item in @props.selection.items()
|
||||
Actions.destroyDraft(item.accountId, item.headerMessageId)
|
||||
Actions.destroyDraft(item)
|
||||
@props.selection.clear()
|
||||
return
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
|
@ -12,7 +12,6 @@ export default class PrintWindow {
|
|||
// call window.print() after we've cleaned up the dom for printing
|
||||
const scriptPath = path.join(__dirname, '..', 'static', 'print.js');
|
||||
const stylesPath = path.join(__dirname, '..', 'static', 'print-styles.css');
|
||||
const imgPath = path.join(__dirname, '..', 'assets', 'nylas-print-logo.png');
|
||||
const participantsHtml = participants.map((part) => {
|
||||
return (`<li class="participant"><span>${part.name} <${part.email}></span></li>`);
|
||||
}).join('');
|
||||
|
@ -30,7 +29,6 @@ export default class PrintWindow {
|
|||
Print
|
||||
</div>
|
||||
<div class="logo-wrapper">
|
||||
<img src="${imgPath}" alt="nylas-logo"/>
|
||||
<span class="account">${account.name} <${account.email}></span>
|
||||
</div>
|
||||
<h1>${subject}</h1>
|
||||
|
|
|
@ -212,12 +212,6 @@ xdescribe "DraftEditingSession Specs", ->
|
|||
@session.changes.commit().then =>
|
||||
expect(updated.body).toBe "123"
|
||||
|
||||
it "doesn't queues a SyncbackDraftTask if no Syncback is passed", ->
|
||||
spyOn(DatabaseStore, "run").andReturn(Promise.resolve(@draft))
|
||||
waitsForPromise =>
|
||||
@session.changes.commit({noSyncback: true}).then =>
|
||||
expect(Actions.queueTask).not.toHaveBeenCalled()
|
||||
|
||||
describe "when findBy does not return a draft", ->
|
||||
it "continues and persists it's local draft reference, so it is resaved and draft editing can continue", ->
|
||||
spyOn(DatabaseStore, "run").andReturn(Promise.resolve(null))
|
||||
|
|
|
@ -142,7 +142,7 @@ class ListTabular extends Component {
|
|||
// If our view has been swapped out for an entirely different one,
|
||||
// reset our scroll position to the top.
|
||||
if (prevProps.dataSource !== this.props.dataSource) {
|
||||
this.refs.container.scrollTop = 0;
|
||||
this._scrollRegion.scrollTop = 0;
|
||||
}
|
||||
|
||||
if (!this.updateRangeStateFiring) {
|
||||
|
@ -222,16 +222,19 @@ class ListTabular extends Component {
|
|||
}
|
||||
|
||||
scrollTo(node) {
|
||||
this.refs.container.scrollTo(node);
|
||||
if (!this._scrollRegion) { return; }
|
||||
this._scrollRegion.scrollTo(node);
|
||||
}
|
||||
|
||||
scrollByPage(direction) {
|
||||
const height = ReactDOM.findDOMNode(this.refs.container).clientHeight;
|
||||
this.refs.container.scrollTop += height * direction;
|
||||
if (!this._scrollRegion) { return; }
|
||||
const height = ReactDOM.findDOMNode(this._scrollRegion).clientHeight;
|
||||
this._scrollRegion.scrollTop += height * direction;
|
||||
}
|
||||
|
||||
updateRangeState() {
|
||||
const {scrollTop} = this.refs.container;
|
||||
if (!this._scrollRegion) { return; }
|
||||
const {scrollTop} = this._scrollRegion;
|
||||
const {itemHeight} = this.props
|
||||
|
||||
// Determine the exact range of rows we want onscreen
|
||||
|
@ -344,7 +347,7 @@ class ListTabular extends Component {
|
|||
return (
|
||||
<div className="list-container list-tabular #{@props.className}">
|
||||
<ScrollRegion
|
||||
ref="container"
|
||||
ref={(cm) => { this._scrollRegion = cm; }}
|
||||
onScroll={this.onScroll}
|
||||
tabIndex="-1"
|
||||
scrollTooltipComponent={scrollTooltipComponent}
|
||||
|
|
|
@ -220,7 +220,10 @@ export default class OverlaidComponents extends React.Component {
|
|||
const toggle = (previewToggleVisible) ? this._renderPreviewToggle() : false;
|
||||
|
||||
return (
|
||||
<div className="overlaid-components" ref={(el) => { this._overlaidComponentsEl = el; }}>
|
||||
<div
|
||||
className="overlaid-components"
|
||||
ref={(el) => { if (el) { this._overlaidComponentsEl = el; } }}
|
||||
>
|
||||
{toggle}
|
||||
{els}
|
||||
</div>
|
||||
|
@ -231,7 +234,10 @@ export default class OverlaidComponents extends React.Component {
|
|||
const {className} = this.props
|
||||
return (
|
||||
<div className={`overlaid-components-wrap ${className || ''}`} style={{position: "relative"}}>
|
||||
<div className="anchor-container" ref={(el) => { this._anchorContainerEl = el; }} >
|
||||
<div
|
||||
className="anchor-container"
|
||||
ref={(el) => { if (el) { this._anchorContainerEl = el; } }}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
{this._renderOverlaidComponents()}
|
||||
|
|
|
@ -80,8 +80,7 @@ function InflatesDraftClientId(ComposedComponent) {
|
|||
return;
|
||||
}
|
||||
if (this.state.draft.pristine) {
|
||||
const {accountId, headerMessageId} = this.state.draft;
|
||||
Actions.destroyDraft(accountId, headerMessageId);
|
||||
Actions.destroyDraft(this.state.draft);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -181,7 +181,8 @@ export default class MailsyncBridge {
|
|||
if (!this._clients[accountId]) {
|
||||
const err = new Error(`No mailsync worker is running.`);
|
||||
err.accountId = accountId;
|
||||
throw err;
|
||||
NylasEnv.reportError(err);
|
||||
return;
|
||||
}
|
||||
this._clients[accountId].sendMessage(json);
|
||||
}
|
||||
|
|
|
@ -393,6 +393,7 @@ class AttachmentStore extends NylasStore {
|
|||
}
|
||||
|
||||
const file = new File({
|
||||
id: Utils.generateTempId(),
|
||||
filename: filename,
|
||||
size: stats.size,
|
||||
contentType: null,
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'underscore'
|
|||
import EventEmitter from 'events';
|
||||
import NylasStore from 'nylas-store';
|
||||
|
||||
import TaskQueue from './task-queue';
|
||||
import Message from '../models/message'
|
||||
import Actions from '../actions'
|
||||
import AccountStore from './account-store'
|
||||
|
@ -11,6 +12,7 @@ import UndoStack from '../../undo-stack'
|
|||
import DraftHelpers from '../stores/draft-helpers'
|
||||
import {Composer as ComposerExtensionRegistry} from '../../registries/extension-registry'
|
||||
import SyncbackDraftTask from '../tasks/syncback-draft-task'
|
||||
import DestroyDraftTask from '../tasks/destroy-draft-task'
|
||||
|
||||
const MetadataChangePrefix = 'metadata.';
|
||||
let DraftStore = null;
|
||||
|
@ -68,7 +70,7 @@ class DraftChangeSet extends EventEmitter {
|
|||
this.add(changes, {doesNotAffectPristine: true});
|
||||
}
|
||||
|
||||
commit({noSyncback} = {}) {
|
||||
commit() {
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
|
@ -79,7 +81,7 @@ class DraftChangeSet extends EventEmitter {
|
|||
|
||||
this._saving = this._pending;
|
||||
this._pending = {};
|
||||
return this.callbacks.onCommit({noSyncback}).then(() => {
|
||||
return this.callbacks.onCommit().then(() => {
|
||||
this._saving = {}
|
||||
});
|
||||
};
|
||||
|
@ -232,26 +234,51 @@ export default class DraftEditingSession extends NylasStore {
|
|||
|
||||
// This function makes sure the draft is attached to a valid account, and changes
|
||||
// it's accountId if the from address does not match the account for the from
|
||||
// address
|
||||
// address.
|
||||
//
|
||||
// If the account is updated it makes a request to delete the draft with the
|
||||
// old accountId
|
||||
async ensureCorrectAccount({noSyncback} = {}) {
|
||||
const account = AccountStore.accountForEmail(this._draft.from[0].email);
|
||||
async ensureCorrectAccount() {
|
||||
const draft = this.draft();
|
||||
const account = AccountStore.accountForEmail(draft.from[0].email);
|
||||
if (!account) {
|
||||
throw new Error("DraftEditingSession::ensureCorrectAccount - you can only send drafts from a configured account.");
|
||||
}
|
||||
|
||||
if (account.id !== this._draft.accountId) {
|
||||
// todo bg decide what to do here to sync
|
||||
// NylasAPIHelpers.makeDraftDeletionRequest(this._draft)
|
||||
this.changes.add({
|
||||
accountId: account.id,
|
||||
version: null,
|
||||
threadId: null,
|
||||
replyToMessageId: null,
|
||||
if (account.id !== draft.accountId) {
|
||||
// Create a new draft in the new account (with a new ID).
|
||||
// Because we use the headerMessageId /exclusively/ as the
|
||||
// identifier we'll be fine.
|
||||
//
|
||||
// Then destroy the old one, since it may be synced to the server
|
||||
// and require cleanup!
|
||||
//
|
||||
const create = new SyncbackDraftTask({
|
||||
headerMessageId: draft.headerMessageId,
|
||||
draft: new Message({
|
||||
from: draft.from,
|
||||
version: 0,
|
||||
to: draft.to,
|
||||
cc: draft.cc,
|
||||
bcc: draft.bcc,
|
||||
body: draft.body,
|
||||
files: draft.files,
|
||||
replyTo: draft.replyTo,
|
||||
subject: draft.subject,
|
||||
headerMessageId: draft.headerMessageId,
|
||||
accountId: account.id,
|
||||
unread: false,
|
||||
starred: false,
|
||||
draft: true,
|
||||
}),
|
||||
});
|
||||
await this.changes.commit({noSyncback});
|
||||
|
||||
const destroy = new DestroyDraftTask({
|
||||
messageIds: [draft.id],
|
||||
accountId: draft.accountId,
|
||||
});
|
||||
|
||||
Actions.queueTask(create);
|
||||
await TaskQueue.waitForPerformLocal(create);
|
||||
Actions.queueTask(destroy);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -334,13 +361,12 @@ export default class DraftEditingSession extends NylasStore {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO BG noSyncback is gone
|
||||
async changeSetCommit() {
|
||||
if (this._destroyed || !this._draft) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set a variable here to protect againg this._draft getting set from
|
||||
// Set a variable here to protect against this._draft getting set from
|
||||
// underneath us
|
||||
const inMemoryDraft = this._draft;
|
||||
const draft = await DatabaseStore
|
||||
|
@ -356,7 +382,6 @@ export default class DraftEditingSession extends NylasStore {
|
|||
// by creating a new draft
|
||||
const baseDraft = draft || inMemoryDraft;
|
||||
const updatedDraft = this.changes.applyToModel(baseDraft);
|
||||
console.log("Queueing SyncbackDraftTask");
|
||||
Actions.queueTask(new SyncbackDraftTask({draft: updatedDraft}));
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ class DraftStore extends NylasStore {
|
|||
if (draft && draft.pristine) {
|
||||
Actions.queueTask(new DestroyDraftTask({
|
||||
accountId: draft.accountId,
|
||||
headerMessageId: draft.headerMessageId,
|
||||
messageIds: [draft.headerMessageId],
|
||||
}));
|
||||
} else {
|
||||
promises.push(session.changes.commit());
|
||||
|
@ -316,7 +316,7 @@ class DraftStore extends NylasStore {
|
|||
});
|
||||
}
|
||||
|
||||
_onDestroyDraft = (accountId, headerMessageId) => {
|
||||
_onDestroyDraft = ({accountId, headerMessageId, id}) => {
|
||||
const session = this._draftSessions[headerMessageId];
|
||||
|
||||
// Immediately reset any pending changes so no saves occur
|
||||
|
@ -335,7 +335,7 @@ class DraftStore extends NylasStore {
|
|||
})
|
||||
|
||||
// Queue the task to destroy the draft
|
||||
Actions.queueTask(new DestroyDraftTask({accountId, headerMessageId}));
|
||||
Actions.queueTask(new DestroyDraftTask({accountId, messageIds: [id]}));
|
||||
|
||||
if (NylasEnv.isComposerWindow()) {
|
||||
NylasEnv.close();
|
||||
|
|
|
@ -2,10 +2,9 @@ import Task from './task';
|
|||
import Attributes from '../attributes';
|
||||
|
||||
export default class DestroyDraftTask extends Task {
|
||||
|
||||
static attributes = Object.assign({}, Task.attributes, {
|
||||
headerMessageId: Attributes.String({
|
||||
modelKey: 'headerMessageId',
|
||||
messageIds: Attributes.Collection({
|
||||
modelKey: 'messageIds',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue