Destroy drafts using ids not headerMessageIds

This commit is contained in:
Ben Gotow 2017-09-05 13:15:37 -07:00
parent ad8b538763
commit f051b52e8c
16 changed files with 94 additions and 62 deletions

View file

@ -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}

View file

@ -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}

View file

@ -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">

View file

@ -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

View file

@ -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

View file

@ -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} &lt;${part.email}&gt;</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} &lt;${account.email}&gt;</span>
</div>
<h1>${subject}</h1>

View file

@ -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))

View file

@ -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}

View file

@ -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()}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -393,6 +393,7 @@ class AttachmentStore extends NylasStore {
}
const file = new File({
id: Utils.generateTempId(),
filename: filename,
size: stats.size,
contentType: null,

View file

@ -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}));
}

View file

@ -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();

View file

@ -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',
}),
});
}