diff --git a/app/internal_packages/composer/lib/composer-editor.jsx b/app/internal_packages/composer/lib/composer-editor.jsx index 791753775..5a77cbe0c 100644 --- a/app/internal_packages/composer/lib/composer-editor.jsx +++ b/app/internal_packages/composer/lib/composer-editor.jsx @@ -270,7 +270,7 @@ class ComposerEditor extends Component { shouldAcceptDrop={this._shouldAcceptDrop} > { this._contenteditableComponent = cm; }} + ref={(cm) => { if (cm) { this._contenteditableComponent = cm; } }} value={this.props.body} onChange={this.props.onBodyChanged} onFilePaste={this.props.onFilePaste} diff --git a/app/internal_packages/composer/lib/composer-header.jsx b/app/internal_packages/composer/lib/composer-header.jsx index 0ab7f4b3e..3cdec195e 100644 --- a/app/internal_packages/composer/lib/composer-header.jsx +++ b/app/internal_packages/composer/lib/composer-header.jsx @@ -214,7 +214,7 @@ export default class ComposerHeader extends React.Component { return ( { 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 ( { this._els.subjectContainer = el; }} + ref={(el) => { if (el) { this._els.subjectContainer = el; } }} onFocusIn={this._onFocusInSubject} onFocusOut={this._onFocusOutSubject} > { 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( { 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( { 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( { 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( { 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} diff --git a/app/internal_packages/composer/lib/composer-view.jsx b/app/internal_packages/composer/lib/composer-view.jsx index dcdd262ee..85122ec86 100644 --- a/app/internal_packages/composer/lib/composer-view.jsx +++ b/app/internal_packages/composer/lib/composer-view.jsx @@ -148,12 +148,15 @@ export default class ComposerView extends React.Component { } } + _setSREl = (el) => { + this._els.scrollregion = el; + } _renderContentScrollRegion() { if (NylasEnv.isComposerWindow()) { return ( { this._els.scrollregion = el; }} + ref={(el) => { if (el) { this._els.scrollregion = el; } }} > {this._renderContent()} @@ -172,7 +175,7 @@ export default class ComposerView extends React.Component { return (
{ 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 { />
{ 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 ( -
{ this._els.composerBodyWrap = el; }} className="composer-body-wrap"> +
{ if (el) { this._els.composerBodyWrap = el; } }} + className="composer-body-wrap" + > {this._renderEditor()} @@ -221,7 +227,7 @@ export default class ComposerView extends React.Component { return ( { 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 { { 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 { { this._els.composerWrap = el; }} + ref={(el) => { if (el) { this._els.composerWrap = el; } }} tabIndex="-1" > diff --git a/app/internal_packages/draft-list/lib/draft-list.cjsx b/app/internal_packages/draft-list/lib/draft-list.cjsx index 24321f7ae..f74fec175 100644 --- a/app/internal_packages/draft-list/lib/draft-list.cjsx +++ b/app/internal_packages/draft-list/lib/draft-list.cjsx @@ -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 diff --git a/app/internal_packages/draft-list/lib/draft-toolbar-buttons.cjsx b/app/internal_packages/draft-list/lib/draft-toolbar-buttons.cjsx index 81894ba52..2ed9744e7 100644 --- a/app/internal_packages/draft-list/lib/draft-toolbar-buttons.cjsx +++ b/app/internal_packages/draft-list/lib/draft-toolbar-buttons.cjsx @@ -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 diff --git a/app/internal_packages/print/assets/nylas-print-logo.png b/app/internal_packages/print/assets/nylas-print-logo.png deleted file mode 100644 index 43d3269c5..000000000 Binary files a/app/internal_packages/print/assets/nylas-print-logo.png and /dev/null differ diff --git a/app/internal_packages/print/lib/print-window.es6 b/app/internal_packages/print/lib/print-window.es6 index a9351038e..c12ef0907 100644 --- a/app/internal_packages/print/lib/print-window.es6 +++ b/app/internal_packages/print/lib/print-window.es6 @@ -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 (`
  • ${part.name} <${part.email}>
  • `); }).join(''); @@ -30,7 +29,6 @@ export default class PrintWindow { Print
    - nylas-logo

    ${subject}

    diff --git a/app/spec/stores/draft-editing-session-spec.coffee b/app/spec/stores/draft-editing-session-spec.coffee index 304f79eff..2ef0bfea0 100644 --- a/app/spec/stores/draft-editing-session-spec.coffee +++ b/app/spec/stores/draft-editing-session-spec.coffee @@ -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)) diff --git a/app/src/components/list-tabular.jsx b/app/src/components/list-tabular.jsx index d57448261..e06cc54c1 100644 --- a/app/src/components/list-tabular.jsx +++ b/app/src/components/list-tabular.jsx @@ -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 (
    { this._scrollRegion = cm; }} onScroll={this.onScroll} tabIndex="-1" scrollTooltipComponent={scrollTooltipComponent} diff --git a/app/src/components/overlaid-components/overlaid-components.jsx b/app/src/components/overlaid-components/overlaid-components.jsx index f789a8949..de36ed022 100644 --- a/app/src/components/overlaid-components/overlaid-components.jsx +++ b/app/src/components/overlaid-components/overlaid-components.jsx @@ -220,7 +220,10 @@ export default class OverlaidComponents extends React.Component { const toggle = (previewToggleVisible) ? this._renderPreviewToggle() : false; return ( -
    { this._overlaidComponentsEl = el; }}> +
    { if (el) { this._overlaidComponentsEl = el; } }} + > {toggle} {els}
    @@ -231,7 +234,10 @@ export default class OverlaidComponents extends React.Component { const {className} = this.props return (
    -
    { this._anchorContainerEl = el; }} > +
    { if (el) { this._anchorContainerEl = el; } }} + > {this.props.children}
    {this._renderOverlaidComponents()} diff --git a/app/src/decorators/inflates-draft-client-id.jsx b/app/src/decorators/inflates-draft-client-id.jsx index 24120baa6..b5a957064 100644 --- a/app/src/decorators/inflates-draft-client-id.jsx +++ b/app/src/decorators/inflates-draft-client-id.jsx @@ -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); } } diff --git a/app/src/flux/mailsync-bridge.es6 b/app/src/flux/mailsync-bridge.es6 index 17db75deb..98d86ccf9 100644 --- a/app/src/flux/mailsync-bridge.es6 +++ b/app/src/flux/mailsync-bridge.es6 @@ -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); } diff --git a/app/src/flux/stores/attachment-store.es6 b/app/src/flux/stores/attachment-store.es6 index 37673ad8d..f31b0da40 100644 --- a/app/src/flux/stores/attachment-store.es6 +++ b/app/src/flux/stores/attachment-store.es6 @@ -393,6 +393,7 @@ class AttachmentStore extends NylasStore { } const file = new File({ + id: Utils.generateTempId(), filename: filename, size: stats.size, contentType: null, diff --git a/app/src/flux/stores/draft-editing-session.es6 b/app/src/flux/stores/draft-editing-session.es6 index 7b4d8a25a..72447063c 100644 --- a/app/src/flux/stores/draft-editing-session.es6 +++ b/app/src/flux/stores/draft-editing-session.es6 @@ -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})); } diff --git a/app/src/flux/stores/draft-store.es6 b/app/src/flux/stores/draft-store.es6 index 40737612a..bf3b07200 100644 --- a/app/src/flux/stores/draft-store.es6 +++ b/app/src/flux/stores/draft-store.es6 @@ -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(); diff --git a/app/src/flux/tasks/destroy-draft-task.es6 b/app/src/flux/tasks/destroy-draft-task.es6 index 24543c79a..4d8b1343e 100644 --- a/app/src/flux/tasks/destroy-draft-task.es6 +++ b/app/src/flux/tasks/destroy-draft-task.es6 @@ -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', }), }); }