mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-20 19:44:52 +08:00
Draft creation, saving, deletion moved to c++
This commit is contained in:
parent
3c56e2fbfd
commit
4966ae3650
45 changed files with 277 additions and 344 deletions
|
@ -7,7 +7,7 @@ class TemplatePopover extends React.Component {
|
|||
static displayName = 'TemplatePopover';
|
||||
|
||||
static propTypes = {
|
||||
draftClientId: React.PropTypes.string,
|
||||
headerMessageId: React.PropTypes.string,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
@ -45,7 +45,7 @@ class TemplatePopover extends React.Component {
|
|||
};
|
||||
|
||||
_onChooseTemplate = (template) => {
|
||||
Actions.insertTemplateId({templateId: template.id, draftClientId: this.props.draftClientId});
|
||||
Actions.insertTemplateId({templateId: template.id, headerMessageId: this.props.headerMessageId});
|
||||
Actions.closePopover();
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ class TemplatePopover extends React.Component {
|
|||
};
|
||||
|
||||
_onNewTemplate = () => {
|
||||
Actions.createTemplate({draftClientId: this.props.draftClientId});
|
||||
Actions.createTemplate({headerMessageId: this.props.headerMessageId});
|
||||
};
|
||||
|
||||
_onClickButton = () => {
|
||||
|
@ -103,13 +103,13 @@ class TemplatePicker extends React.Component {
|
|||
static displayName = 'TemplatePicker';
|
||||
|
||||
static propTypes = {
|
||||
draftClientId: React.PropTypes.string,
|
||||
headerMessageId: React.PropTypes.string,
|
||||
};
|
||||
|
||||
_onClickButton = () => {
|
||||
const buttonRect = ReactDOM.findDOMNode(this).getBoundingClientRect()
|
||||
Actions.openPopover(
|
||||
<TemplatePopover draftClientId={this.props.draftClientId} />,
|
||||
<TemplatePopover headerMessageId={this.props.headerMessageId} />,
|
||||
{originRect: buttonRect, direction: 'up'}
|
||||
)
|
||||
};
|
||||
|
|
|
@ -112,9 +112,9 @@ class TemplateStore extends NylasStore {
|
|||
});
|
||||
}
|
||||
|
||||
_onCreateTemplate({draftClientId, name, contents} = {}) {
|
||||
if (draftClientId) {
|
||||
DraftStore.sessionForClientId(draftClientId).then((session) => {
|
||||
_onCreateTemplate({headerMessageId, name, contents} = {}) {
|
||||
if (headerMessageId) {
|
||||
DraftStore.sessionForClientId(headerMessageId).then((session) => {
|
||||
const draft = session.draft();
|
||||
const draftName = name || draft.subject.replace(TemplateStore.INVALID_TEMPLATE_NAME_REGEX, '');
|
||||
let draftContents = contents || QuotedHTMLTransformer.removeQuotedHTML(draft.body);
|
||||
|
@ -255,9 +255,9 @@ class TemplateStore extends NylasStore {
|
|||
});
|
||||
}
|
||||
|
||||
_onInsertTemplateId({templateId, draftClientId} = {}) {
|
||||
_onInsertTemplateId({templateId, headerMessageId} = {}) {
|
||||
this.getTemplateContents(templateId, (templateBody) => {
|
||||
DraftStore.sessionForClientId(draftClientId).then((session) => {
|
||||
DraftStore.sessionForClientId(headerMessageId).then((session) => {
|
||||
let proceed = true;
|
||||
if (!session.draft().pristine && !session.draft().hasEmptyBody()) {
|
||||
proceed = this._displayDialog(
|
||||
|
|
|
@ -83,7 +83,7 @@ xdescribe('TemplateStore', function templateStore() {
|
|||
runs(() => {
|
||||
TemplateStore._onInsertTemplateId({
|
||||
templateId: 'template1.html',
|
||||
draftClientId: 'localid-draft',
|
||||
headerMessageId: 'localid-draft',
|
||||
});
|
||||
});
|
||||
waitsFor(() => add.calls.length > 0);
|
||||
|
@ -98,8 +98,8 @@ xdescribe('TemplateStore', function templateStore() {
|
|||
describe('onCreateTemplate', () => {
|
||||
beforeEach(() => {
|
||||
let d;
|
||||
spyOn(DraftStore, 'sessionForClientId').andCallFake((draftClientId) => {
|
||||
if (draftClientId === 'localid-nosubject') {
|
||||
spyOn(DraftStore, 'sessionForClientId').andCallFake((headerMessageId) => {
|
||||
if (headerMessageId === 'localid-nosubject') {
|
||||
d = new Message({subject: '', body: '<p>Body</p>'});
|
||||
} else {
|
||||
d = new Message({subject: 'Subject', body: '<p>Body</p>'});
|
||||
|
@ -149,7 +149,7 @@ xdescribe('TemplateStore', function templateStore() {
|
|||
spyOn(TemplateStore, 'trigger');
|
||||
spyOn(TemplateStore, '_populate');
|
||||
runs(() => {
|
||||
TemplateStore._onCreateTemplate({draftClientId: 'localid-b'});
|
||||
TemplateStore._onCreateTemplate({headerMessageId: 'localid-b'});
|
||||
});
|
||||
waitsFor(() => TemplateStore.trigger.callCount > 0);
|
||||
runs(() => {
|
||||
|
@ -161,7 +161,7 @@ xdescribe('TemplateStore', function templateStore() {
|
|||
spyOn(TemplateStore, '_displayError');
|
||||
spyOn(fs, 'watch');
|
||||
runs(() => {
|
||||
TemplateStore._onCreateTemplate({draftClientId: 'localid-nosubject'});
|
||||
TemplateStore._onCreateTemplate({headerMessageId: 'localid-nosubject'});
|
||||
});
|
||||
waitsFor(() => TemplateStore._displayError.callCount > 0);
|
||||
runs(() => {
|
||||
|
|
|
@ -7,14 +7,14 @@ export default class ComposerHeaderActions extends React.Component {
|
|||
static displayName = 'ComposerHeaderActions';
|
||||
|
||||
static propTypes = {
|
||||
draftClientId: React.PropTypes.string.isRequired,
|
||||
headerMessageId: React.PropTypes.string.isRequired,
|
||||
enabledFields: React.PropTypes.array.isRequired,
|
||||
participantsFocused: React.PropTypes.bool,
|
||||
onShowAndFocusField: React.PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
_onPopoutComposer = () => {
|
||||
Actions.composePopoutDraft(this.props.draftClientId);
|
||||
Actions.composePopoutDraft(this.props.headerMessageId);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -326,7 +326,7 @@ export default class ComposerHeader extends React.Component {
|
|||
return (
|
||||
<div className="composer-header">
|
||||
<ComposerHeaderActions
|
||||
draftId={this.props.draft.id}
|
||||
headerMessageId={this.props.draft.headerMessageId}
|
||||
enabledFields={this.state.enabledFields}
|
||||
participantsFocused={this.state.participantsFocused}
|
||||
onShowAndFocusField={this.showAndFocusField}
|
||||
|
|
|
@ -205,7 +205,7 @@ export default class ComposerView extends React.Component {
|
|||
_renderEditor() {
|
||||
const exposedProps = {
|
||||
body: this.props.draft.body,
|
||||
draftClientId: this.props.draft.clientId,
|
||||
headerMessageId: this.props.draft.headerMessageId,
|
||||
parentActions: {
|
||||
getComposerBoundingRect: this._getComposerBoundingRect,
|
||||
scrollTo: this.props.scrollTo,
|
||||
|
@ -292,7 +292,7 @@ export default class ComposerView extends React.Component {
|
|||
exposedProps={{
|
||||
draft: this.props.draft,
|
||||
threadId: this.props.draft.threadId,
|
||||
draftClientId: this.props.draft.clientId,
|
||||
headerMessageId: this.props.draft.headerMessageId,
|
||||
session: this.props.session,
|
||||
}}
|
||||
direction="column"
|
||||
|
@ -311,11 +311,11 @@ export default class ComposerView extends React.Component {
|
|||
}
|
||||
|
||||
_renderFileAttachments() {
|
||||
const {files, clientId: messageClientId} = this.props.draft
|
||||
const {files, headerMessageId} = this.props.draft
|
||||
return (
|
||||
<InjectedComponent
|
||||
matching={{role: 'MessageAttachments'}}
|
||||
exposedProps={{files, messageClientId, canRemoveAttachments: true}}
|
||||
exposedProps={{files, headerMessageId, canRemoveAttachments: true}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -365,7 +365,7 @@ export default class ComposerView extends React.Component {
|
|||
exposedProps={{
|
||||
draft: this.props.draft,
|
||||
threadId: this.props.draft.threadId,
|
||||
draftClientId: this.props.draft.clientId,
|
||||
headerMessageId: this.props.draft.headerMessageId,
|
||||
session: this.props.session,
|
||||
}}
|
||||
/>
|
||||
|
@ -415,7 +415,7 @@ export default class ComposerView extends React.Component {
|
|||
]}
|
||||
exposedProps={{
|
||||
draft: this.props.draft,
|
||||
draftClientId: this.props.draft.clientId,
|
||||
headerMessageId: this.props.draft.headerMessageId,
|
||||
session: this.props.session,
|
||||
isValidDraft: this._isValidDraft,
|
||||
}}
|
||||
|
@ -510,7 +510,7 @@ export default class ComposerView extends React.Component {
|
|||
// called from onDrop and onFilePaste - assume images should be inline
|
||||
Actions.addAttachment({
|
||||
filePath: filePath,
|
||||
messageClientId: this.props.draft.clientId,
|
||||
headerMessageId: this.props.draft.headerMessageId,
|
||||
onUploadCreated: (upload) => {
|
||||
if (Utils.shouldDisplayAsImage(upload)) {
|
||||
const {draft, session} = this.props;
|
||||
|
@ -522,7 +522,7 @@ export default class ComposerView extends React.Component {
|
|||
session.changes.add({uploads})
|
||||
|
||||
Actions.insertAttachmentIntoDraft({
|
||||
draftClientId: draft.clientId,
|
||||
headerMessageId: draft.headerMessageId,
|
||||
uploadId: matchingUpload.id,
|
||||
});
|
||||
}
|
||||
|
@ -541,7 +541,7 @@ export default class ComposerView extends React.Component {
|
|||
// immediately and synchronously updated as soon as this function
|
||||
// fires. Since `setState` is asynchronous, if we used that as our only
|
||||
// check, then we might get a false reading.
|
||||
if (DraftStore.isSendingDraft(this.props.draft.clientId)) {
|
||||
if (DraftStore.isSendingDraft(this.props.draft.headerMessageId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -579,11 +579,11 @@ export default class ComposerView extends React.Component {
|
|||
}
|
||||
|
||||
_onDestroyDraft = () => {
|
||||
Actions.destroyDraft(this.props.draft.clientId);
|
||||
Actions.destroyDraft(this.props.draft.headerMessageId);
|
||||
}
|
||||
|
||||
_onSelectAttachment = () => {
|
||||
Actions.selectAttachment({messageClientId: this.props.draft.clientId});
|
||||
Actions.selectAttachment({headerMessageId: this.props.draft.headerMessageId});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class ImageUploadComposerExtension extends ComposerExtension {
|
|||
}
|
||||
|
||||
static _onInsertAttachmentIntoDraft({editor, actionArg}) {
|
||||
if (editor.draftClientId === actionArg.draftClientId) { return }
|
||||
if (editor.headerMessageId === actionArg.headerMessageId) { return }
|
||||
|
||||
editor.insertCustomComponent("InlineImageUploadContainer", {
|
||||
className: `inline-container-${actionArg.uploadId}`,
|
||||
|
|
|
@ -29,12 +29,12 @@ class ComposerWithWindowProps extends React.Component {
|
|||
|
||||
// We'll now always have windowProps by the time we construct this.
|
||||
const windowProps = NylasEnv.getWindowProps();
|
||||
const {draftJSON, draftClientId} = windowProps;
|
||||
const {draftJSON, headerMessageId} = windowProps;
|
||||
if (!draftJSON) {
|
||||
throw new Error("Initialize popout composer windows with valid draftJSON")
|
||||
}
|
||||
const draft = new Message().fromJSON(draftJSON);
|
||||
DraftStore._createSession(draftClientId, draft);
|
||||
DraftStore._createSession(headerMessageId, draft);
|
||||
this.state = windowProps
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ class ComposerWithWindowProps extends React.Component {
|
|||
<ComposerViewForDraftClientId
|
||||
ref="composer"
|
||||
onDraftReady={this._onDraftReady}
|
||||
draftClientId={this.state.draftClientId}
|
||||
headerMessageId={this.state.headerMessageId}
|
||||
className="composer-full-window"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ describe "ComposerHeaderActions", ->
|
|||
@onShowAndFocusField = jasmine.createSpy("onShowAndFocusField")
|
||||
props.onShowAndFocusField = @onShowAndFocusField
|
||||
props.enabledFields ?= []
|
||||
props.draftClientId = 'a'
|
||||
props.headerMessageId = 'a'
|
||||
@component = ReactTestUtils.renderIntoDocument(
|
||||
<ComposerHeaderActions {...props} />
|
||||
)
|
||||
|
|
|
@ -58,7 +58,7 @@ class DraftListStore extends NylasStore
|
|||
|
||||
mailboxPerspective.accountIds.forEach (aid) =>
|
||||
OutboxStore.itemsForAccount(aid).forEach (task) =>
|
||||
draft = resultSet.modelWithId(task.draftClientId)
|
||||
draft = resultSet.modelWithId(task.headerMessageId)
|
||||
if draft
|
||||
draft = draft.clone()
|
||||
draft.uploadTaskId = task.id
|
||||
|
|
|
@ -41,12 +41,12 @@ class DraftList extends React.Component
|
|||
|
||||
_onDoubleClick: (draft) =>
|
||||
unless draft.uploadTaskId
|
||||
Actions.composePopoutDraft(draft.id)
|
||||
Actions.composePopoutDraft(draft.headerMessageId)
|
||||
|
||||
# Additional Commands
|
||||
|
||||
_onRemoveFromView: =>
|
||||
drafts = DraftListStore.dataSource().selection.items()
|
||||
Actions.destroyDraft(draft.id) for draft in drafts
|
||||
Actions.destroyDraft(draft.headerMessageId) 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.id)
|
||||
Actions.destroyDraft(item.headerMessageId)
|
||||
@props.selection.clear()
|
||||
return
|
||||
|
||||
|
|
|
@ -64,11 +64,11 @@ class MessageItemContainer extends React.Component
|
|||
_renderComposer: =>
|
||||
Composer = ComponentRegistry.findComponentsMatching(role: 'Composer')[0]
|
||||
if (!Composer)
|
||||
return <span></span>
|
||||
return <span>No Composer Component Present</span>
|
||||
|
||||
<Composer
|
||||
ref="message"
|
||||
draftId={@props.message.id}
|
||||
headerMessageId={@props.message.headerMessageId}
|
||||
className={@_classNames()}
|
||||
mode={"inline"}
|
||||
threadId={@props.thread.id}
|
||||
|
@ -82,8 +82,8 @@ class MessageItemContainer extends React.Component
|
|||
"message-item-wrap": true
|
||||
"before-reply-area": @props.isBeforeReplyArea
|
||||
|
||||
_onSendingStateChanged: (draftId) =>
|
||||
if draftId is @props.message.id
|
||||
_onSendingStateChanged: (headerMessageId) =>
|
||||
if headerMessageId is @props.message.headerMessageId
|
||||
@setState(@_getStateFromStores())
|
||||
|
||||
_getStateFromStores: (props = @props) ->
|
||||
|
|
|
@ -37,7 +37,7 @@ xdescribe 'MessageItemContainer', ->
|
|||
ReactTestUtils.renderIntoDocument(
|
||||
<MessageItemContainer thread={testThread}
|
||||
message={message}
|
||||
draftClientId={testClientId} />
|
||||
headerMessageId={testClientId} />
|
||||
)
|
||||
|
||||
it "shows composer if it's a draft", ->
|
||||
|
|
|
@ -50,7 +50,7 @@ m1 = (new Message).fromJSON({
|
|||
"draft" : false
|
||||
"files" : [],
|
||||
"unread" : false,
|
||||
"object" : "message",
|
||||
"__constructorName": "Message",
|
||||
"snippet" : "snippet one...",
|
||||
"subject" : "Subject One",
|
||||
"thread_id" : "thread_12345",
|
||||
|
@ -67,7 +67,7 @@ m2 = (new Message).fromJSON({
|
|||
"draft" : false
|
||||
"files" : [],
|
||||
"unread" : false,
|
||||
"object" : "message",
|
||||
"__constructorName": "Message",
|
||||
"snippet" : "snippet Two...",
|
||||
"subject" : "Subject Two",
|
||||
"thread_id" : "thread_12345",
|
||||
|
@ -84,7 +84,7 @@ m3 = (new Message).fromJSON({
|
|||
"draft" : false
|
||||
"files" : [],
|
||||
"unread" : false,
|
||||
"object" : "message",
|
||||
"__constructorName": "Message",
|
||||
"snippet" : "snippet Three...",
|
||||
"subject" : "Subject Three",
|
||||
"thread_id" : "thread_12345",
|
||||
|
@ -101,7 +101,7 @@ m4 = (new Message).fromJSON({
|
|||
"draft" : false
|
||||
"files" : [],
|
||||
"unread" : false,
|
||||
"object" : "message",
|
||||
"__constructorName": "Message",
|
||||
"snippet" : "snippet Four...",
|
||||
"subject" : "Subject Four",
|
||||
"thread_id" : "thread_12345",
|
||||
|
@ -118,7 +118,7 @@ m5 = (new Message).fromJSON({
|
|||
"draft" : false
|
||||
"files" : [],
|
||||
"unread" : false,
|
||||
"object" : "message",
|
||||
"__constructorName": "Message",
|
||||
"snippet" : "snippet Five...",
|
||||
"subject" : "Subject Five",
|
||||
"thread_id" : "thread_12345",
|
||||
|
@ -137,7 +137,7 @@ draftMessages = [
|
|||
"draft" : true
|
||||
"files" : [],
|
||||
"unread" : false,
|
||||
"object" : "draft",
|
||||
"__constructorName": "Message",
|
||||
"snippet" : "draft snippet one...",
|
||||
"subject" : "Draft One",
|
||||
"thread_id" : "thread_12345",
|
||||
|
|
|
@ -90,7 +90,7 @@ class SnoozeStore {
|
|||
// Get messages for those threads and metadata for those.
|
||||
DatabaseStore.findAll(Message, {threadId: update.threads.map(t => t.id)}).then((messages) => {
|
||||
for (const message of messages) {
|
||||
const header = message.messageIdHeader;
|
||||
const header = message.headerMessageId;
|
||||
const stableId = message.id;
|
||||
Actions.setMetadata(message, this.pluginId,
|
||||
{expiration: snoozeDate, header, stableId, snoozeCategoryId, returnCategoryId})
|
||||
|
|
|
@ -4,7 +4,7 @@ var fs = require('fs');
|
|||
var path = require('path');
|
||||
|
||||
// node build/node_modules/.bin/grunt "$@"
|
||||
var gruntPath = path.resolve(__dirname, '..', 'build', 'node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
var gruntPath = path.resolve(__dirname, '..', 'node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
|
||||
if (!fs.existsSync(gruntPath)) {
|
||||
console.error('Grunt command does not exist at: ' + gruntPath);
|
||||
|
|
|
@ -12,14 +12,14 @@ ipc =
|
|||
|
||||
describe "ActionBridge", ->
|
||||
|
||||
describe "in the work window", ->
|
||||
describe "in the womainrk window", ->
|
||||
beforeEach ->
|
||||
spyOn(NylasEnv, "getWindowType").andReturn "default"
|
||||
spyOn(NylasEnv, "isWorkWindow").andReturn true
|
||||
@bridge = new ActionBridge(ipc)
|
||||
|
||||
it "should have the role Role.WORK", ->
|
||||
expect(@bridge.role).toBe(ActionBridge.Role.WORK)
|
||||
it "should have the role Role.MAIN", ->
|
||||
expect(@bridge.role).toBe(ActionBridge.Role.MAIN)
|
||||
|
||||
it "should rebroadcast global actions", ->
|
||||
spyOn(@bridge, 'onRebroadcast')
|
||||
|
@ -87,12 +87,12 @@ describe "ActionBridge", ->
|
|||
@bridge.onRebroadcast(ActionBridge.TargetWindows.ALL, 'onNewMailDeltas', [{oldModel: '1', newModel: 2}])
|
||||
expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-all', 'popout', 'onNewMailDeltas', '[{"oldModel":"1","newModel":2}]')
|
||||
|
||||
describe "when called with TargetWindows.WORK", ->
|
||||
describe "when called with TargetWindows.MAIN", ->
|
||||
it "should broadcast the action over IPC to the main window only", ->
|
||||
spyOn(ipc, 'send')
|
||||
Actions.onNewMailDeltas.firing = false
|
||||
@bridge.onRebroadcast(ActionBridge.TargetWindows.WORK, 'onNewMailDeltas', [{oldModel: '1', newModel: 2}])
|
||||
expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-work', 'popout', 'onNewMailDeltas', '[{"oldModel":"1","newModel":2}]')
|
||||
@bridge.onRebroadcast(ActionBridge.TargetWindows.MAIN, 'onNewMailDeltas', [{oldModel: '1', newModel: 2}])
|
||||
expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-default', 'popout', 'onNewMailDeltas', '[{"oldModel":"1","newModel":2}]')
|
||||
|
||||
it "should not do anything if the current invocation of the Action was triggered by itself", ->
|
||||
spyOn(ipc, 'send')
|
||||
|
|
|
@ -3,7 +3,7 @@ AccountStore = require("../../src/flux/stores/account-store").default
|
|||
|
||||
json_event =
|
||||
{
|
||||
"object": "event",
|
||||
"__constructorName": "Event",
|
||||
"id": "4ee4xbnx7pxdb9g7c2f8ncyto",
|
||||
"calendar_id": "ci0k1wfyv533ccgox4t7uri4h",
|
||||
"account_id": "14e5bn96uizyuhidhcw5rfrb0",
|
||||
|
|
|
@ -9,7 +9,7 @@ describe 'Thread', ->
|
|||
describe 'serialization performance', ->
|
||||
xit '1,000,000 iterations', ->
|
||||
iterations = 0
|
||||
json = '[{"client_id":"local-76c370af-65de","server_id":"f0vkowp7zxt7djue7ifylb940","object":"thread","account_id":"1r6w6qiq3sb0o9fiwin6v87dd","snippet":"http://itunestandc.tumblr.com/tagged/itunes-terms-and-conditions/chrono _______________________________________________ http://www.macgroup.com/mailman/listinfo/smartfriends-chat","subject":"iTunes Terms And Conditions as you\'ve never seen them before","unread":true,"starred":false,"version":1,"folders":[],"labels":[{"server_id":"8cf4fn20k9pjjhjawrv3xrxo0","name":"all","display_name":"All Mail","id":"8cf4fn20k9pjjhjawrv3xrxo0"},{"server_id":"f1lq8faw8vv06m67y8f3xdf84","name":"inbox","display_name":"Inbox","id":"f1lq8faw8vv06m67y8f3xdf84"}],"participants":[{"name":"Andrew Stadler","email":"stadler@gmail.com","thirdPartyData":{}},{"name":"Smart Friends™ Chat","email":"smartfriends-chat@macgroup.com","thirdPartyData":{}}],"has_attachments":false,"last_message_received_timestamp":1446600615,"id":"f0vkowp7zxt7djue7ifylb940"}]'
|
||||
json = '[{"client_id":"local-76c370af-65de","server_id":"f0vkowp7zxt7djue7ifylb940","__constructorName":"Thread","account_id":"1r6w6qiq3sb0o9fiwin6v87dd","snippet":"http://itunestandc.tumblr.com/tagged/itunes-terms-and-conditions/chrono _______________________________________________ http://www.macgroup.com/mailman/listinfo/smartfriends-chat","subject":"iTunes Terms And Conditions as you\'ve never seen them before","unread":true,"starred":false,"version":1,"folders":[],"labels":[{"server_id":"8cf4fn20k9pjjhjawrv3xrxo0","name":"all","display_name":"All Mail","id":"8cf4fn20k9pjjhjawrv3xrxo0"},{"server_id":"f1lq8faw8vv06m67y8f3xdf84","name":"inbox","display_name":"Inbox","id":"f1lq8faw8vv06m67y8f3xdf84"}],"participants":[{"name":"Andrew Stadler","email":"stadler@gmail.com","thirdPartyData":{}},{"name":"Smart Friends™ Chat","email":"smartfriends-chat@macgroup.com","thirdPartyData":{}}],"has_attachments":false,"last_message_received_timestamp":1446600615,"id":"f0vkowp7zxt7djue7ifylb940"}]'
|
||||
start = Date.now()
|
||||
while iterations < 1000000
|
||||
if _.isString(json)
|
||||
|
|
|
@ -26,7 +26,7 @@ describe "AccountStore", ->
|
|||
"client_id" : 'local-4f9d476a-c173',
|
||||
"server_id" : 'A',
|
||||
"email_address":"bengotow@gmail.com",
|
||||
"object":"account"
|
||||
"__constructorName":"Account"
|
||||
"organization_unit": "label"
|
||||
"aliases": ["Alias <alias@nylas.com>"]
|
||||
},{
|
||||
|
@ -34,7 +34,7 @@ describe "AccountStore", ->
|
|||
"client_id" : 'local-4f9d476a-c175',
|
||||
"server_id" : 'B',
|
||||
"email_address":"ben@nylas.com",
|
||||
"object":"account"
|
||||
"__constructorName":"Account"
|
||||
"organization_unit": "label"
|
||||
}]
|
||||
|
||||
|
@ -95,7 +95,7 @@ describe "AccountStore", ->
|
|||
"server_id" : 'B',
|
||||
"email_address":"ben@nylas.com",
|
||||
"provider":"gmail",
|
||||
"object":"account",
|
||||
"__constructorName":"Account",
|
||||
"organization_unit": "label",
|
||||
@instance = new @constructor
|
||||
spyOn(NylasEnv.config, "set")
|
||||
|
@ -127,7 +127,7 @@ describe "AccountStore", ->
|
|||
"server_id" : 'B',
|
||||
"email_address":"ben@nylas.com",
|
||||
"provider":"gmail",
|
||||
"object":"account"
|
||||
"__constructorName":"Account"
|
||||
"organization_unit": "label"
|
||||
@spyOnConfig()
|
||||
@instance = new @constructor
|
||||
|
@ -144,7 +144,7 @@ describe "AccountStore", ->
|
|||
"server_id" : 'NEVER SEEN BEFORE',
|
||||
"email_address":"ben@nylas.com",
|
||||
"provider":"gmail",
|
||||
"object":"account"
|
||||
"__constructorName":"Account"
|
||||
"organization_unit": "label"
|
||||
@spyOnConfig()
|
||||
@instance = new @constructor
|
||||
|
|
|
@ -132,9 +132,9 @@ xdescribe 'FileUploadStore', ->
|
|||
|
||||
describe "when a draft is sent", ->
|
||||
it "should delete its uploads directory", ->
|
||||
spyOn(FileUploadStore, '_deleteUploadsForClientId')
|
||||
spyOn(FileUploadStore, '_deleteUploadsForId')
|
||||
Actions.ensureMessageInSentSuccess({messageClientId: '123'})
|
||||
expect(FileUploadStore._deleteUploadsForClientId).toHaveBeenCalledWith('123')
|
||||
expect(FileUploadStore._deleteUploadsForId).toHaveBeenCalledWith('123')
|
||||
|
||||
describe '_getFileStats', ->
|
||||
it 'returns the correct stats', ->
|
||||
|
|
|
@ -23,7 +23,6 @@ describe("UndoRedoStore", function undoRedoStoreSpec() {
|
|||
UndoRedoStore._undo = []
|
||||
UndoRedoStore._redo = []
|
||||
spyOn(UndoRedoStore, "trigger")
|
||||
spyOn(Actions, "undoTaskId")
|
||||
spyOn(Actions, "queueTask").andCallFake((...args) => {
|
||||
UndoRedoStore._onQueue(...args)
|
||||
});
|
||||
|
@ -83,9 +82,9 @@ describe("UndoRedoStore", function undoRedoStoreSpec() {
|
|||
it("runs undoTask on each group of undo tasks", () => {
|
||||
UndoRedoStore._undo = [[this.t3], [this.t1, this.t2]]
|
||||
UndoRedoStore.undo()
|
||||
expect(Actions.undoTaskId.calls.length).toBe(2)
|
||||
expect(Actions.undoTaskId.calls[0].args[0]).toBe("t1")
|
||||
expect(Actions.undoTaskId.calls[1].args[0]).toBe("t2")
|
||||
expect(Actions.queueTask.calls.length).toBe(2)
|
||||
expect(Actions.queueTask.calls[0].args[0]).toBe("t1")
|
||||
expect(Actions.queueTask.calls[1].args[0]).toBe("t2")
|
||||
expect(UndoRedoStore._undo).toEqual([[this.t3]])
|
||||
});
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ xdescribe('BaseDraftTask', function baseDraftTask() {
|
|||
badTask.performLocal().then(() => {
|
||||
throw new Error("Shouldn't succeed")
|
||||
}).catch((err) => {
|
||||
expect(err.message).toBe("Attempt to call BaseDraftTask.performLocal without a draftClientId")
|
||||
expect(err.message).toBe("Attempt to call BaseDraftTask.performLocal without a headerMessageId")
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -456,7 +456,7 @@ export default class Application extends EventEmitter {
|
|||
|
||||
ipcMain.on('ensure-worker-window', () => {
|
||||
// TODO BG
|
||||
// this.windowManager.ensureWindow(WindowManager.WORK_WINDOW)
|
||||
// this.windowManager.ensureWindow(WindowManager.MAIN_WINDOW)
|
||||
})
|
||||
|
||||
ipcMain.on('inline-style-parse', (event, {html, key}) => {
|
||||
|
@ -523,16 +523,15 @@ export default class Application extends EventEmitter {
|
|||
this.windowManager.sendToAllWindows('action-bridge-message', {except: win}, ...args)
|
||||
});
|
||||
|
||||
ipcMain.on('action-bridge-rebroadcast-to-work', (event, ...args) => {
|
||||
// TODO BG
|
||||
// const workWindow = this.windowManager.get(WindowManager.WORK_WINDOW)
|
||||
// if (!workWindow || !workWindow.browserWindow.webContents) {
|
||||
// return;
|
||||
// }
|
||||
// if (BrowserWindow.fromWebContents(event.sender) === workWindow) {
|
||||
// return;
|
||||
// }
|
||||
// workWindow.browserWindow.webContents.send('action-bridge-message', ...args);
|
||||
ipcMain.on('action-bridge-rebroadcast-to-default', (event, ...args) => {
|
||||
const mainWindow = this.windowManager.get(WindowManager.MAIN_WINDOW)
|
||||
if (!mainWindow || !mainWindow.browserWindow.webContents) {
|
||||
return;
|
||||
}
|
||||
if (BrowserWindow.fromWebContents(event.sender) === mainWindow) {
|
||||
return;
|
||||
}
|
||||
mainWindow.browserWindow.webContents.send('action-bridge-message', ...args);
|
||||
});
|
||||
|
||||
ipcMain.on('write-text-to-selection-clipboard', (event, selectedText) => {
|
||||
|
@ -542,7 +541,6 @@ export default class Application extends EventEmitter {
|
|||
|
||||
ipcMain.on('account-setup-successful', () => {
|
||||
this.windowManager.ensureWindow(WindowManager.MAIN_WINDOW);
|
||||
this.windowManager.ensureWindow(WindowManager.WORK_WINDOW);
|
||||
const onboarding = this.windowManager.get(WindowManager.ONBOARDING_WINDOW);
|
||||
if (onboarding) {
|
||||
onboarding.close();
|
||||
|
|
|
@ -14,11 +14,11 @@ components inside of your React render method. Rather than explicitly render
|
|||
a component, such as a `<Composer>`, you can use InjectedComponent:
|
||||
|
||||
```coffee
|
||||
<InjectedComponent matching={role:"Composer"} exposedProps={draftClientId:123} />
|
||||
<InjectedComponent matching={role:"Composer"} exposedProps={headerMessageId:123} />
|
||||
```
|
||||
|
||||
InjectedComponent will look up the component registered with that role in the
|
||||
{ComponentRegistry} and render it, passing the exposedProps (`draftClientId={123}`) along.
|
||||
{ComponentRegistry} and render it, passing the exposedProps (`headerMessageId={123}`) along.
|
||||
|
||||
InjectedComponent monitors the ComponentRegistry for changes. If a new component
|
||||
is registered that matches the descriptor you provide, InjectedComponent will refresh.
|
||||
|
|
|
@ -8,7 +8,7 @@ function InflatesDraftClientId(ComposedComponent) {
|
|||
static displayName = ComposedComponent.displayName;
|
||||
|
||||
static propTypes = {
|
||||
draftClientId: React.PropTypes.string,
|
||||
headerMessageId: React.PropTypes.string,
|
||||
onDraftReady: React.PropTypes.func,
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ function InflatesDraftClientId(ComposedComponent) {
|
|||
|
||||
componentDidMount() {
|
||||
this._mounted = true;
|
||||
this._prepareForDraft(this.props.draftClientId);
|
||||
this._prepareForDraft(this.props.headerMessageId);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -38,19 +38,19 @@ function InflatesDraftClientId(ComposedComponent) {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.draftClientId !== this.props.draftClientId) {
|
||||
if (newProps.headerMessageId !== this.props.headerMessageId) {
|
||||
this._teardownForDraft();
|
||||
this._prepareForDraft(newProps.draftClientId);
|
||||
this._prepareForDraft(newProps.headerMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
_prepareForDraft(draftClientId) {
|
||||
if (!draftClientId) {
|
||||
_prepareForDraft(headerMessageId) {
|
||||
if (!headerMessageId) {
|
||||
return;
|
||||
}
|
||||
DraftStore.sessionForClientId(draftClientId).then((session) => {
|
||||
DraftStore.sessionForClientId(headerMessageId).then((session) => {
|
||||
const shouldSetState = () =>
|
||||
this._mounted && session.draftClientId === this.props.draftClientId
|
||||
this._mounted && session.headerMessageId === this.props.headerMessageId
|
||||
|
||||
if (!shouldSetState()) { return; }
|
||||
this._sessionUnlisten = session.listen(() => {
|
||||
|
@ -80,7 +80,7 @@ function InflatesDraftClientId(ComposedComponent) {
|
|||
return;
|
||||
}
|
||||
if (this.state.draft.pristine) {
|
||||
Actions.destroyDraft(this.props.draftClientId);
|
||||
Actions.destroyDraft(this.props.headerMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ import net from 'net';
|
|||
import fs from 'fs';
|
||||
import DatabaseStore from './stores/database-store';
|
||||
import DatabaseChangeRecord from './stores/database-change-record';
|
||||
|
||||
import DatabaseObjectRegistry from '../registries/database-object-registry';
|
||||
import Actions from './actions';
|
||||
import Utils from './models/utils';
|
||||
|
||||
class ActionBridgeCPP {
|
||||
|
@ -13,6 +14,13 @@ class ActionBridgeCPP {
|
|||
return;
|
||||
}
|
||||
|
||||
Actions.queueTask.listen(this.onQueueTask, this);
|
||||
Actions.queueTasks.listen((tasks) => {
|
||||
if (!tasks || !tasks.length) { return; }
|
||||
for (const task of tasks) { this.onQueueTask(task); }
|
||||
});
|
||||
Actions.dequeueTask.listen(this.onDequeueTask, this);
|
||||
|
||||
try {
|
||||
fs.unlinkSync('/tmp/cmail.sock');
|
||||
} catch (err) {
|
||||
|
@ -41,7 +49,7 @@ class ActionBridgeCPP {
|
|||
});
|
||||
});
|
||||
|
||||
unixServer.listen('/tmp/cmail.sock', () => {
|
||||
unixServer.listen('/tmp/cmail.sock', () => {
|
||||
console.log('server bound');
|
||||
});
|
||||
|
||||
|
@ -54,6 +62,29 @@ class ActionBridgeCPP {
|
|||
process.on('SIGINT', shutdown);
|
||||
}
|
||||
|
||||
onQueueTask(task) {
|
||||
// if (!(task instanceof Task)) {
|
||||
// console.log(task);
|
||||
// throw new Error("You must queue a `Task` instance. Be sure you have the task registered with the DatabaseObjectRegistry. If this is a task for a custom plugin, you must export a `taskConstructors` array with your `Task` constructors in it. You must all subclass the base Nylas `Task`.");
|
||||
// }
|
||||
if (!DatabaseObjectRegistry.isInRegistry(task.constructor.name)) {
|
||||
console.log(task);
|
||||
throw new Error("You must queue a `Task` instance which is registred with the DatabaseObjectRegistry")
|
||||
}
|
||||
if (!task.id) {
|
||||
console.log(task);
|
||||
throw new Error("Tasks must have an ID prior to being queued. Check that your Task constructor is calling `super`");
|
||||
}
|
||||
task.sequentialId = ++this._currentSequentialId;
|
||||
task.status = 'local';
|
||||
|
||||
this.onTellClients({type: 'task-queued', task: task});
|
||||
}
|
||||
|
||||
onDequeueTask() { // task
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
onIncomingMessage(message) {
|
||||
this._readBuffer += message;
|
||||
const msgs = this._readBuffer.split('\n');
|
||||
|
|
|
@ -6,13 +6,13 @@ import DatabaseChangeRecord from './stores/database-change-record';
|
|||
import Utils from './models/utils';
|
||||
|
||||
const Role = {
|
||||
WORK: 'work',
|
||||
MAIN: 'default',
|
||||
SECONDARY: 'secondary',
|
||||
};
|
||||
|
||||
const TargetWindows = {
|
||||
ALL: 'all',
|
||||
WORK: 'work',
|
||||
MAIN: 'default',
|
||||
};
|
||||
|
||||
const Message = {
|
||||
|
@ -49,7 +49,7 @@ class ActionBridge {
|
|||
this.ipc = ipc;
|
||||
this.ipcLastSendTime = null;
|
||||
this.initiatorId = NylasEnv.getWindowType();
|
||||
this.role = NylasEnv.isWorkWindow() ? Role.WORK : Role.SECONDARY;
|
||||
this.role = NylasEnv.isMainWindow() ? Role.MAIN : Role.SECONDARY;
|
||||
|
||||
NylasEnv.onBeforeUnload(this.onBeforeUnload);
|
||||
|
||||
|
@ -76,11 +76,11 @@ class ActionBridge {
|
|||
};
|
||||
DatabaseStore.listen(databaseCallback, this);
|
||||
|
||||
if (this.role !== Role.WORK) {
|
||||
if (this.role !== Role.MAIN) {
|
||||
// Observe all mainWindow actions fired in this window and re-broadcast
|
||||
// them to other windows so the central application stores can take action
|
||||
Actions.workWindowActions.forEach(name => {
|
||||
const callback = (...args) => this.onRebroadcast(TargetWindows.WORK, name, args);
|
||||
Actions.mainWindowActions.forEach(name => {
|
||||
const callback = (...args) => this.onRebroadcast(TargetWindows.MAIN, name, args);
|
||||
return Actions[name].listen(callback, this);
|
||||
});
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ class ActionBridge {
|
|||
const params = [];
|
||||
args.forEach((arg) => {
|
||||
if (arg instanceof Function) {
|
||||
throw new Error("ActionBridge cannot forward action argument of type `function` to work window.");
|
||||
throw new Error("ActionBridge cannot forward action argument of type `function` to another window.");
|
||||
}
|
||||
return params.push(arg);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import Reflux from 'reflux';
|
|||
|
||||
const ActionScopeWindow = 'window';
|
||||
const ActionScopeGlobal = 'global';
|
||||
const ActionScopeWorkWindow = 'work';
|
||||
const ActionScopeMainWindow = 'main';
|
||||
|
||||
/*
|
||||
Public: In the Flux {Architecture.md}, almost every user action
|
||||
|
@ -81,36 +81,33 @@ class Actions {
|
|||
/*
|
||||
Public: Queue a {Task} object to the {TaskQueue}.
|
||||
|
||||
*Scope: Work Window*
|
||||
*Scope: Main Window*
|
||||
*/
|
||||
static queueTask = ActionScopeWorkWindow;
|
||||
static queueTask = ActionScopeMainWindow;
|
||||
|
||||
/*
|
||||
Public: Queue multiple {Task} objects to the {TaskQueue}, which should be
|
||||
undone as a single user action.
|
||||
|
||||
*Scope: Work Window*
|
||||
*Scope: Main Window*
|
||||
*/
|
||||
static queueTasks = ActionScopeWorkWindow;
|
||||
|
||||
static undoTaskId = ActionScopeWorkWindow;
|
||||
|
||||
static queueTasks = ActionScopeMainWindow;
|
||||
/*
|
||||
Public: Dequeue all {Task}s from the {TaskQueue}. Use with care.
|
||||
|
||||
*Scope: Work Window*
|
||||
*Scope: Main Window*
|
||||
*/
|
||||
static dequeueTask = ActionScopeWorkWindow;
|
||||
static dequeueTask = ActionScopeMainWindow;
|
||||
|
||||
/*
|
||||
Public: Dequeue a {Task} matching the description provided.
|
||||
|
||||
*Scope: Work Window*
|
||||
*Scope: Main Window*
|
||||
*/
|
||||
static longPollReceivedRawDeltas = ActionScopeWorkWindow;
|
||||
static longPollProcessedDeltas = ActionScopeWorkWindow;
|
||||
static willMakeAPIRequest = ActionScopeWorkWindow;
|
||||
static didMakeAPIRequest = ActionScopeWorkWindow;
|
||||
static longPollReceivedRawDeltas = ActionScopeMainWindow;
|
||||
static longPollProcessedDeltas = ActionScopeMainWindow;
|
||||
static willMakeAPIRequest = ActionScopeMainWindow;
|
||||
static didMakeAPIRequest = ActionScopeMainWindow;
|
||||
static checkOnlineStatus = ActionScopeWindow;
|
||||
|
||||
|
||||
|
@ -456,7 +453,7 @@ class Actions {
|
|||
```
|
||||
Actions.removeFile
|
||||
file: fileObject
|
||||
messageClientId: draftClientId
|
||||
headerMessageId: headerMessageId
|
||||
```
|
||||
*/
|
||||
static removeFile = ActionScopeWindow;
|
||||
|
@ -510,7 +507,7 @@ class Actions {
|
|||
/*
|
||||
Public: Publish a user event to any analytics services linked to N1.
|
||||
*/
|
||||
static recordUserEvent = ActionScopeWorkWindow;
|
||||
static recordUserEvent = ActionScopeMainWindow;
|
||||
|
||||
static addMailRule = ActionScopeWindow;
|
||||
static reorderMailRule = ActionScopeWindow;
|
||||
|
@ -570,14 +567,14 @@ const create = (obj, name, scope) => {
|
|||
const scopes = {
|
||||
window: [],
|
||||
global: [],
|
||||
work: [],
|
||||
main: [],
|
||||
};
|
||||
|
||||
for (const name of Object.getOwnPropertyNames(Actions)) {
|
||||
if (name === 'length' || name === 'name' || name === 'arguments' || name === 'caller' || name === 'prototype') {
|
||||
continue;
|
||||
}
|
||||
if (Actions[name] !== 'window' && Actions[name] !== 'global' && Actions[name] !== 'work') {
|
||||
if (Actions[name] !== 'window' && Actions[name] !== 'global' && Actions[name] !== 'main') {
|
||||
continue;
|
||||
}
|
||||
const scope = Actions[name];
|
||||
|
@ -586,7 +583,7 @@ for (const name of Object.getOwnPropertyNames(Actions)) {
|
|||
}
|
||||
|
||||
Actions.windowActions = scopes.window;
|
||||
Actions.workWindowActions = scopes.work;
|
||||
Actions.mainWindowActions = scopes.main;
|
||||
Actions.globalActions = scopes.global;
|
||||
|
||||
export default Actions;
|
||||
|
|
|
@ -139,8 +139,9 @@ export default class Message extends ModelWithMetadata {
|
|||
modelKey: 'threadId',
|
||||
}),
|
||||
|
||||
messageIdHeader: Attributes.String({
|
||||
modelKey: 'messageIdHeader',
|
||||
headerMessageId: Attributes.String({
|
||||
queryable: true,
|
||||
modelKey: 'headerMessageId',
|
||||
}),
|
||||
|
||||
subject: Attributes.String({
|
||||
|
|
|
@ -27,9 +27,6 @@ class Model
|
|||
queryable: true
|
||||
modelKey: 'id'
|
||||
|
||||
'object': Attributes.String
|
||||
modelKey: 'object'
|
||||
|
||||
'accountId': Attributes.String
|
||||
queryable: true
|
||||
modelKey: 'accountId'
|
||||
|
@ -96,6 +93,7 @@ class Model
|
|||
continue if attr instanceof Attributes.AttributeJoinedData and options.joined is false
|
||||
json[attr.jsonKey] = attr.toJSON(attrValue)
|
||||
json["id"] = @id
|
||||
json["__constructorName"] = this.constructor.name
|
||||
json
|
||||
|
||||
toString: ->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {AccountStore} from 'nylas-exports'
|
||||
import AccountStore from './stores/account-store'
|
||||
import NylasLongConnection from './nylas-long-connection'
|
||||
|
||||
// A 0 code is when an error returns without a status code, like "ESOCKETTIMEDOUT"
|
||||
|
|
|
@ -3,7 +3,6 @@ import path from 'path';
|
|||
import createDebug from 'debug';
|
||||
import childProcess from 'child_process';
|
||||
import PromiseQueue from 'promise-queue';
|
||||
import {remote} from 'electron';
|
||||
import LRU from "lru-cache";
|
||||
import {ExponentialBackoffScheduler} from '../../backoff-schedulers';
|
||||
|
||||
|
@ -111,21 +110,6 @@ class DatabaseStore extends NylasStore {
|
|||
this._emitter.emit('ready')
|
||||
}
|
||||
|
||||
// When 3rd party components register new models, we need to refresh the
|
||||
// database schema to prepare those tables. This method may be called
|
||||
// extremely frequently as new models are added when packages load.
|
||||
refreshDatabaseSchema() {
|
||||
if (!NylasEnv.isMainWindow()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const app = remote.getGlobal('application');
|
||||
const phase = app.databasePhase();
|
||||
if (phase !== DatabasePhase.Setup) {
|
||||
app.setDatabasePhase(DatabasePhase.Setup);
|
||||
}
|
||||
return this._asyncWaitForReady()
|
||||
}
|
||||
|
||||
_prettyConsoleLog(qa) {
|
||||
let q = qa.replace(/%/g, '%%');
|
||||
q = `color:black |||%c ${q}`;
|
||||
|
@ -200,7 +184,7 @@ class DatabaseStore extends NylasStore {
|
|||
const msec = Date.now() - start;
|
||||
if (debugVerbose.enabled) {
|
||||
const q = `🔶 (${msec}ms) Background: ${query}`;
|
||||
debugVerbose(StringUtils.trimTo(q))
|
||||
debugVerbose(trimTo(q))
|
||||
}
|
||||
|
||||
if (msec > 100) {
|
||||
|
@ -253,7 +237,7 @@ class DatabaseStore extends NylasStore {
|
|||
const msec = Date.now() - start;
|
||||
if (debugVerbose.enabled) {
|
||||
const q = `(${msec}ms) ${query}`;
|
||||
debugVerbose(StringUtils.trimTo(q))
|
||||
debugVerbose(trimTo(q))
|
||||
}
|
||||
|
||||
if (msec > 100) {
|
||||
|
|
|
@ -102,7 +102,7 @@ class DraftEditingSession
|
|||
@include Publisher
|
||||
@include Listener
|
||||
|
||||
constructor: (@draftId, draft = null) ->
|
||||
constructor: (@headerMessageId, draft = null) ->
|
||||
DraftStore ?= require('./draft-store').default
|
||||
@listenTo DraftStore, @_onDraftChanged
|
||||
|
||||
|
@ -137,9 +137,9 @@ class DraftEditingSession
|
|||
@_draftPristineBody
|
||||
|
||||
prepare: ->
|
||||
@_draftPromise ?= DatabaseStore.findBy(Message, id: @draftId).include(Message.attributes.body).then (draft) =>
|
||||
@_draftPromise ?= DatabaseStore.findBy(Message, headerMessageId: @headerMessageId).include(Message.attributes.body).then (draft) =>
|
||||
return Promise.reject(new Error("Draft has been destroyed.")) if @_destroyed
|
||||
return Promise.reject(new Error("Assertion Failure: Draft #{@draftId} not found.")) if not draft
|
||||
return Promise.reject(new Error("Assertion Failure: Draft #{@headerMessageId} not found.")) if not draft
|
||||
return @_setDraft(draft)
|
||||
|
||||
teardown: ->
|
||||
|
@ -261,31 +261,18 @@ class DraftEditingSession
|
|||
# underneath us
|
||||
inMemoryDraft = @_draft
|
||||
|
||||
DatabaseStore.inTransaction (t) =>
|
||||
t.findBy(Message, id: inMemoryDraft.id).include(Message.attributes.body).then (draft) =>
|
||||
# This can happen if we get a "delete" delta, or something else
|
||||
# strange happens. In this case, we'll use the @_draft we have in
|
||||
# memory to apply the changes to. On the `persistModel` in the
|
||||
# next line it will save the correct changes. The
|
||||
# `SyncbackDraftTask` may then fail due to differing Ids not
|
||||
# existing, but if this happens it'll 404 and recover gracefully
|
||||
# by creating a new draft
|
||||
draft ?= inMemoryDraft
|
||||
updatedDraft = @changes.applyToModel(draft)
|
||||
return t.persistModel(updatedDraft)
|
||||
|
||||
.then =>
|
||||
return if noSyncback
|
||||
# We have temporarily disabled the syncback of most drafts to user's mail
|
||||
# providers, due to a number of issues in the sync-engine that we're still
|
||||
# firefighting.
|
||||
#
|
||||
# For now, drafts are only synced when you choose "Send Later", and then
|
||||
# once they have a serverId we sync them periodically here.
|
||||
#
|
||||
return unless @_draft.serverId
|
||||
Actions.ensureDraftSynced(@draftId)
|
||||
|
||||
DatabaseStore.findBy(Message, id: inMemoryDraft.id).include(Message.attributes.body).then (draft) =>
|
||||
# This can happen if we get a "delete" delta, or something else
|
||||
# strange happens. In this case, we'll use the @_draft we have in
|
||||
# memory to apply the changes to. On the `persistModel` in the
|
||||
# next line it will save the correct changes. The
|
||||
# `SyncbackDraftTask` may then fail due to differing Ids not
|
||||
# existing, but if this happens it'll 404 and recover gracefully
|
||||
# by creating a new draft
|
||||
draft ?= inMemoryDraft
|
||||
updatedDraft = @changes.applyToModel(draft)
|
||||
console.log("Queueing SyncbackDraftTask")
|
||||
Actions.queueTask(new SyncbackDraftTask(updatedDraft))
|
||||
|
||||
# Undo / Redo
|
||||
|
||||
|
|
|
@ -27,7 +27,11 @@ class DraftFactory
|
|||
Promise.resolve(new Message(_.extend({
|
||||
body: ''
|
||||
subject: ''
|
||||
id: Utils.generateTempId()
|
||||
version: 0
|
||||
unread: false
|
||||
starred: false
|
||||
folderImapUID: 0
|
||||
headerMessageId: Utils.generateTempId()
|
||||
from: [account.defaultMe()]
|
||||
date: (new Date)
|
||||
draft: true
|
||||
|
|
|
@ -91,26 +91,26 @@ class DraftStore extends NylasStore {
|
|||
@param {String} clientId - The clientId of the draft.
|
||||
@returns {Promise} - Resolves to an {DraftEditingSession} for the draft once it has been prepared
|
||||
*/
|
||||
sessionForClientId(clientId) {
|
||||
if (!clientId) {
|
||||
sessionForClientId(headerMessageId) {
|
||||
if (!headerMessageId) {
|
||||
throw new Error("DraftStore::sessionForClientId requires a clientId");
|
||||
}
|
||||
if (this._draftSessions[clientId] == null) {
|
||||
this._draftSessions[clientId] = this._createSession(clientId);
|
||||
if (this._draftSessions[headerMessageId] == null) {
|
||||
this._draftSessions[headerMessageId] = this._createSession(headerMessageId);
|
||||
}
|
||||
return this._draftSessions[clientId].prepare();
|
||||
return this._draftSessions[headerMessageId].prepare();
|
||||
}
|
||||
|
||||
// Public: Look up the sending state of the given draftClientId.
|
||||
// Public: Look up the sending state of the given draft headerMessageId.
|
||||
// In popout windows the existance of the window is the sending state.
|
||||
isSendingDraft(draftClientId) {
|
||||
return this._draftsSending[draftClientId] || false;
|
||||
isSendingDraft(headerMessageId) {
|
||||
return this._draftsSending[headerMessageId] || false;
|
||||
}
|
||||
|
||||
|
||||
_doneWithSession(session) {
|
||||
session.teardown();
|
||||
delete this._draftSessions[session.draftClientId];
|
||||
delete this._draftSessions[session.headerMessageId];
|
||||
}
|
||||
|
||||
_cleanupAllSessions() {
|
||||
|
@ -129,7 +129,7 @@ class DraftStore extends NylasStore {
|
|||
_.each(this._draftSessions, (session) => {
|
||||
const draft = session.draft()
|
||||
if (draft && draft.pristine) {
|
||||
Actions.queueTask(new DestroyDraftTask(session.draftClientId));
|
||||
Actions.queueTask(new DestroyDraftTask(session.headerMessageId));
|
||||
} else {
|
||||
promises.push(session.changes.commit());
|
||||
}
|
||||
|
@ -172,10 +172,10 @@ class DraftStore extends NylasStore {
|
|||
.then((draft) => {
|
||||
draft.body = `${body}\n\n${draft.body}`
|
||||
draft.pristine = false;
|
||||
return DatabaseStore.inTransaction((t) => {
|
||||
return t.persistModel(draft);
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
const t = new SyncbackDraftTask(draft)
|
||||
Actions.queueTask(t)
|
||||
TaskQueue.waitForPerformLocal(t).then(() => {
|
||||
Actions.sendDraft(draft.clientId);
|
||||
});
|
||||
});
|
||||
|
@ -249,17 +249,17 @@ class DraftStore extends NylasStore {
|
|||
// doesn't need to do a query for it a second from now when the composer wants it.
|
||||
this._createSession(draft.clientId, draft);
|
||||
|
||||
return DatabaseStore.inTransaction((t) => {
|
||||
return t.persistModel(draft);
|
||||
})
|
||||
.then(() => {
|
||||
const task = new SyncbackDraftTask(draft);
|
||||
Actions.queueTask(task)
|
||||
|
||||
return TaskQueue.waitForPerformLocal(task).then(() => {
|
||||
if (popout) {
|
||||
this._onPopoutDraftClientId(draft.clientId);
|
||||
this._onPopoutDraftClientId(draft.headerMessageId);
|
||||
} else {
|
||||
Actions.focusDraft({draftClientId: draft.clientId});
|
||||
Actions.focusDraft({headerMessageId: draft.headerMessageId});
|
||||
}
|
||||
return {headerMessageId: draft.headerMessageId, draft};
|
||||
})
|
||||
.thenReturn({draftClientId: draft.clientId, draft});
|
||||
}
|
||||
|
||||
_createSession(clientId, draft) {
|
||||
|
@ -276,18 +276,18 @@ class DraftStore extends NylasStore {
|
|||
_onPopoutBlankDraft = () => {
|
||||
Actions.recordUserEvent("Draft Created", {type: "new"});
|
||||
return DraftFactory.createDraft().then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft).then(({draftClientId}) => {
|
||||
return this._onPopoutDraftClientId(draftClientId, {newDraft: true});
|
||||
return this._finalizeAndPersistNewMessage(draft).then(({headerMessageId}) => {
|
||||
return this._onPopoutDraftClientId(headerMessageId, {newDraft: true});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_onPopoutDraftClientId = (draftClientId, options = {}) => {
|
||||
if (draftClientId == null) {
|
||||
throw new Error("DraftStore::onPopoutDraftId - You must provide a draftClientId");
|
||||
_onPopoutDraftClientId = (headerMessageId, options = {}) => {
|
||||
if (headerMessageId == null) {
|
||||
throw new Error("DraftStore::onPopoutDraftId - You must provide a headerMessageId");
|
||||
}
|
||||
const title = options.newDraft ? "New Message" : "Message";
|
||||
return this.sessionForClientId(draftClientId).then((session) => {
|
||||
return this.sessionForClientId(headerMessageId).then((session) => {
|
||||
return session.changes.commit().then(() => {
|
||||
const draftJSON = session.draft().toJSON();
|
||||
// Since we pass a windowKey, if the popout composer draft already
|
||||
|
@ -296,9 +296,9 @@ class DraftStore extends NylasStore {
|
|||
NylasEnv.newWindow({
|
||||
title,
|
||||
hidden: true, // We manually show in ComposerWithWindowProps::onDraftReady
|
||||
windowKey: `composer-${draftClientId}`,
|
||||
windowKey: `composer-${headerMessageId}`,
|
||||
windowType: 'composer-preload',
|
||||
windowProps: _.extend(options, {draftClientId, draftJSON}),
|
||||
windowProps: _.extend(options, {headerMessageId, draftJSON}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -318,27 +318,27 @@ class DraftStore extends NylasStore {
|
|||
return DraftFactory.createDraft().then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft);
|
||||
})
|
||||
.then(({draftClientId}) => {
|
||||
.then(({headerMessageId}) => {
|
||||
let remaining = paths.length;
|
||||
const callback = () => {
|
||||
remaining -= 1;
|
||||
if (remaining === 0) {
|
||||
this._onPopoutDraftClientId(draftClientId);
|
||||
this._onPopoutDraftClientId(headerMessageId);
|
||||
}
|
||||
};
|
||||
|
||||
paths.forEach((path) => {
|
||||
Actions.addAttachment({
|
||||
filePath: path,
|
||||
messageClientId: draftClientId,
|
||||
messageClientId: headerMessageId,
|
||||
onUploadCreated: callback,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroyDraft = (draftClientId) => {
|
||||
const session = this._draftSessions[draftClientId];
|
||||
_onDestroyDraft = (headerMessageId) => {
|
||||
const session = this._draftSessions[headerMessageId];
|
||||
|
||||
// Immediately reset any pending changes so no saves occur
|
||||
if (session) {
|
||||
|
@ -347,34 +347,34 @@ class DraftStore extends NylasStore {
|
|||
|
||||
// Stop any pending tasks related ot the draft
|
||||
TaskQueue.queue().forEach((task) => {
|
||||
if (task instanceof BaseDraftTask && task.draftClientId === draftClientId) {
|
||||
if (task instanceof BaseDraftTask && task.headerMessageId === headerMessageId) {
|
||||
Actions.dequeueTask(task.id);
|
||||
}
|
||||
})
|
||||
|
||||
// Queue the task to destroy the draft
|
||||
Actions.queueTask(new DestroyDraftTask(draftClientId));
|
||||
Actions.queueTask(new DestroyDraftTask(headerMessageId));
|
||||
|
||||
if (NylasEnv.isComposerWindow()) {
|
||||
NylasEnv.close();
|
||||
}
|
||||
}
|
||||
|
||||
_onEnsureDraftSynced = (draftClientId) => {
|
||||
return this.sessionForClientId(draftClientId).then((session) => {
|
||||
_onEnsureDraftSynced = (headerMessageId) => {
|
||||
return this.sessionForClientId(headerMessageId).then((session) => {
|
||||
return DraftHelpers.prepareDraftForSyncback(session)
|
||||
.then(() => {
|
||||
Actions.queueTask(new SyncbackDraftTask(draftClientId));
|
||||
Actions.queueTask(new SyncbackDraftTask(headerMessageId));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_onSendDraft = (draftClientId, sendActionKey = DefaultSendActionKey) => {
|
||||
this._draftsSending[draftClientId] = true;
|
||||
return this.sessionForClientId(draftClientId).then((session) => {
|
||||
_onSendDraft = (headerMessageId, sendActionKey = DefaultSendActionKey) => {
|
||||
this._draftsSending[headerMessageId] = true;
|
||||
return this.sessionForClientId(headerMessageId).then((session) => {
|
||||
return DraftHelpers.prepareDraftForSyncback(session)
|
||||
.then(() => {
|
||||
Actions.queueTask(new PerformSendActionTask(draftClientId, sendActionKey));
|
||||
Actions.queueTask(new PerformSendActionTask(headerMessageId, sendActionKey));
|
||||
this._doneWithSession(session);
|
||||
if (NylasEnv.config.get("core.sending.sounds")) {
|
||||
SoundRegistry.playSound('hit-send');
|
||||
|
@ -387,7 +387,7 @@ class DraftStore extends NylasStore {
|
|||
}
|
||||
|
||||
__testExtensionTransforms() {
|
||||
const clientId = NylasEnv.getWindowProps().draftClientId;
|
||||
const clientId = NylasEnv.getWindowProps().headerMessageId;
|
||||
return this.sessionForClientId(clientId).then((session) => {
|
||||
return this._prepareForSyncback(session).then(() => {
|
||||
window.__draft = session.draft();
|
||||
|
@ -405,19 +405,19 @@ class DraftStore extends NylasStore {
|
|||
});
|
||||
}
|
||||
|
||||
_onDidCancelSendAction = ({draftClientId}) => {
|
||||
delete this._draftsSending[draftClientId];
|
||||
this.trigger(draftClientId);
|
||||
_onDidCancelSendAction = ({headerMessageId}) => {
|
||||
delete this._draftsSending[headerMessageId];
|
||||
this.trigger(headerMessageId);
|
||||
}
|
||||
|
||||
_onSendDraftSuccess = ({draftClientId}) => {
|
||||
delete this._draftsSending[draftClientId];
|
||||
this.trigger(draftClientId);
|
||||
_onSendDraftSuccess = ({headerMessageId}) => {
|
||||
delete this._draftsSending[headerMessageId];
|
||||
this.trigger(headerMessageId);
|
||||
}
|
||||
|
||||
_onSendDraftFailed = ({draftClientId, threadId, errorMessage, errorDetail}) => {
|
||||
this._draftsSending[draftClientId] = false;
|
||||
this.trigger(draftClientId);
|
||||
_onSendDraftFailed = ({headerMessageId, threadId, errorMessage, errorDetail}) => {
|
||||
this._draftsSending[headerMessageId] = false;
|
||||
this.trigger(headerMessageId);
|
||||
if (NylasEnv.isMainWindow()) {
|
||||
// We delay so the view has time to update the restored draft. If we
|
||||
// don't delay the modal may come up in a state where the draft looks
|
||||
|
@ -426,17 +426,17 @@ class DraftStore extends NylasStore {
|
|||
// We also need to delay because the old draft window needs to fully
|
||||
// close. It takes windows currently (June 2016) 100ms to close by
|
||||
setTimeout(() => {
|
||||
this._notifyUserOfError({draftClientId, threadId, errorMessage, errorDetail});
|
||||
this._notifyUserOfError({headerMessageId, threadId, errorMessage, errorDetail});
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
_notifyUserOfError({draftClientId, threadId, errorMessage, errorDetail}) {
|
||||
_notifyUserOfError({headerMessageId, threadId, errorMessage, errorDetail}) {
|
||||
const focusedThread = FocusedContentStore.focused('thread');
|
||||
if (threadId && focusedThread && focusedThread.id === threadId) {
|
||||
NylasEnv.showErrorDialog(errorMessage, {detail: errorDetail});
|
||||
} else {
|
||||
Actions.composePopoutDraft(draftClientId, {errorMessage, errorDetail});
|
||||
Actions.composePopoutDraft(headerMessageId, {errorMessage, errorDetail});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class FileUploadStore extends NylasStore {
|
|||
mkdirp.sync(UPLOAD_DIR);
|
||||
if (NylasEnv.isMainWindow() || NylasEnv.inSpecMode()) {
|
||||
this.listenTo(Actions.ensureMessageInSentSuccess, ({messageClientId}) => {
|
||||
this._deleteUploadsForClientId(messageClientId);
|
||||
this._deleteUploadsForId(messageClientId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ class FileUploadStore extends NylasStore {
|
|||
.catch((err) => Promise.reject(new Error(`Error deleting file upload ${upload.filename}:\n\n${err.message}`)));
|
||||
}
|
||||
|
||||
_deleteUploadsForClientId(messageClientId) {
|
||||
_deleteUploadsForId(messageClientId) {
|
||||
rimraf(path.join(UPLOAD_DIR, messageClientId), {disableGlob: true}, (err) => {
|
||||
if (err) {
|
||||
console.warn(err);
|
||||
|
|
|
@ -2,8 +2,6 @@ import _ from 'underscore';
|
|||
import NylasStore from 'nylas-store';
|
||||
import {Rx} from 'nylas-exports';
|
||||
import Task from "../tasks/task";
|
||||
import DatabaseObjectRegistry from '../../registries/database-object-registry';
|
||||
import Actions from '../actions';
|
||||
import DatabaseStore from './database-store';
|
||||
|
||||
/**
|
||||
|
@ -52,19 +50,28 @@ class TaskQueue extends NylasStore {
|
|||
this._waitingForRemote = [];
|
||||
|
||||
Rx.Observable.fromQuery(DatabaseStore.findAll(Task)).subscribe((tasks => {
|
||||
this._queue = tasks.filter(t => t.complete === false);
|
||||
this._completed = tasks.filter(t => t.complete === true);
|
||||
this.trigger();
|
||||
// TODO : this._waitingForLocal!
|
||||
}))
|
||||
this._queue = tasks.filter(t => t.status !== 'complete');
|
||||
this._completed = tasks.filter(t => t.status === 'complete');
|
||||
const all = [].concat(this._queue, this._completed);
|
||||
|
||||
this.listenTo(Actions.queueTask, this.enqueue)
|
||||
this.listenTo(Actions.queueTasks, (tasks) => {
|
||||
if (!tasks || !tasks.length) { return; }
|
||||
for (const task of tasks) { this.enqueue(task); }
|
||||
});
|
||||
this.listenTo(Actions.undoTaskId, this.enqueueUndoOfTaskId);
|
||||
this.listenTo(Actions.dequeueTask, this.dequeue);
|
||||
this._waitingForLocal.filter(({task, resolve}) => {
|
||||
if (all.find(t => task.id === t.id)) {
|
||||
resolve();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
this._waitingForRemote.filter(({task, resolve}) => {
|
||||
if (this._completed.find(t => task.id === t.id)) {
|
||||
resolve();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
this.trigger();
|
||||
}));
|
||||
}
|
||||
|
||||
queue() {
|
||||
|
@ -88,7 +95,7 @@ class TaskQueue extends NylasStore {
|
|||
{SaveDraftTask} or 'SaveDraftTask')
|
||||
|
||||
- `matching`: Optional An {Object} with criteria to pass to _.isMatch. For a
|
||||
SaveDraftTask, this could be {draftClientId: "123123"}
|
||||
SaveDraftTask, this could be {headerMessageId: "123123"}
|
||||
|
||||
Returns a matching {Task}, or null.
|
||||
*/
|
||||
|
@ -111,32 +118,6 @@ class TaskQueue extends NylasStore {
|
|||
return matches;
|
||||
}
|
||||
|
||||
enqueue = (task) => {
|
||||
if (!(task instanceof Task)) {
|
||||
console.log(task);
|
||||
throw new Error("You must queue a `Task` instance. Be sure you have the task registered with the DatabaseObjectRegistry. If this is a task for a custom plugin, you must export a `taskConstructors` array with your `Task` constructors in it. You must all subclass the base Nylas `Task`.");
|
||||
}
|
||||
if (!DatabaseObjectRegistry.isInRegistry(task.constructor.name)) {
|
||||
console.log(task);
|
||||
throw new Error("You must queue a `Task` instance which is registred with the DatabaseObjectRegistry")
|
||||
}
|
||||
if (!task.id) {
|
||||
console.log(task);
|
||||
throw new Error("Tasks must have an ID prior to being queued. Check that your Task constructor is calling `super`");
|
||||
}
|
||||
task.sequentialId = ++this._currentSequentialId;
|
||||
task.status = 'local';
|
||||
|
||||
NylasEnv.actionBridgeCpp.onTellClients({type: 'task-queued', task: task});
|
||||
}
|
||||
|
||||
enqueueUndoOfTaskId = (taskId) => {
|
||||
const task = this._queue.find(t => t.id === taskId) || this._completed.find(t => t.id === taskId);
|
||||
if (task) {
|
||||
this.enqueue(task.createUndoTask());
|
||||
}
|
||||
}
|
||||
|
||||
dequeue = (taskOrId) => {
|
||||
const task = this._resolveTaskArgument(taskOrId);
|
||||
if (!task) {
|
||||
|
|
|
@ -43,7 +43,7 @@ class UndoRedoStore extends NylasStore {
|
|||
this.trigger();
|
||||
|
||||
for (const task of topTasks) {
|
||||
Actions.undoTaskId(task.id);
|
||||
Actions.queueTask(task.createUndoTask());
|
||||
}
|
||||
|
||||
const redoTasks = topTasks.map((t) => {
|
||||
|
|
|
@ -3,47 +3,8 @@ import DraftHelpers from '../stores/draft-helpers';
|
|||
|
||||
export default class BaseDraftTask extends Task {
|
||||
|
||||
static DraftNotFoundError = DraftHelpers.DraftNotFoundError;
|
||||
|
||||
constructor(draftClientId) {
|
||||
constructor(draft) {
|
||||
super();
|
||||
this.draftClientId = draftClientId;
|
||||
this.draft = null;
|
||||
}
|
||||
|
||||
shouldDequeueOtherTask(other) {
|
||||
const isSameDraft = (other.draftClientId === this.draftClientId);
|
||||
const isOlderTask = (other.sequentialId < this.sequentialId);
|
||||
const isExactClass = (other.constructor.name === this.constructor.name);
|
||||
return (isSameDraft && isOlderTask && isExactClass);
|
||||
}
|
||||
|
||||
isDependentOnTask(other) {
|
||||
// Set this task to be dependent on any SyncbackDraftTasks and
|
||||
// SendDraftTasks for the same draft that were created first.
|
||||
// This, in conjunction with this method on SendDraftTask, ensures
|
||||
// that a send and a syncback never run at the same time for a draft.
|
||||
|
||||
// Require here rather than on top to avoid a circular dependency
|
||||
const isSameDraft = (other.draftClientId === this.draftClientId);
|
||||
const isOlderTask = (other.sequentialId < this.sequentialId);
|
||||
const isSaveOrSend = (other instanceof BaseDraftTask);
|
||||
return (isSameDraft && isOlderTask && isSaveOrSend);
|
||||
}
|
||||
|
||||
performLocal() {
|
||||
// SyncbackDraftTask does not do anything locally. You should persist your changes
|
||||
// to the local database directly or using a DraftEditingSession, and then queue a
|
||||
// SyncbackDraftTask to send those changes to the server.
|
||||
if (!this.draftClientId) {
|
||||
const errMsg = `Attempt to call ${this.constructor.name}.performLocal without a draftClientId`;
|
||||
return Promise.reject(new Error(errMsg));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async refreshDraftReference() {
|
||||
this.draft = await DraftHelpers.refreshDraftReference(this.draftClientId)
|
||||
return this.draft;
|
||||
this.draft = draft;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
import Task from './task';
|
||||
import {APIError} from '../errors';
|
||||
import Message from '../models/message';
|
||||
import DatabaseStore from '../stores/database-store';
|
||||
import NylasAPI from '../nylas-api';
|
||||
import NylasAPIRequest from '../nylas-api-request';
|
||||
import BaseDraftTask from './base-draft-task';
|
||||
|
||||
export default class DestroyDraftTask extends BaseDraftTask {
|
||||
|
||||
shouldDequeueOtherTask(other) {
|
||||
return (other instanceof BaseDraftTask && other.draftClientId === this.draftClientId);
|
||||
}
|
||||
|
||||
performLocal() {
|
||||
super.performLocal();
|
||||
return this.refreshDraftReference()
|
||||
.then(() => DatabaseStore.inTransaction((t) => t.unpersistModel(this.draft)))
|
||||
.catch(BaseDraftTask.DraftNotFoundError, () => Promise.resolve());
|
||||
constructor(headerMessageId) {
|
||||
super();
|
||||
this.headerMessageId = headerMessageId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import SendActionsStore from '../stores/send-actions-store';
|
|||
|
||||
class PerformSendActionTask extends BaseDraftTask {
|
||||
|
||||
constructor(draftClientId, sendActionKey) {
|
||||
super(draftClientId)
|
||||
constructor(headerMessageId, sendActionKey) {
|
||||
super(headerMessageId)
|
||||
this._sendActionKey = sendActionKey
|
||||
this._sendTimer = null
|
||||
this._taskResolve = () => {}
|
||||
|
@ -21,22 +21,22 @@ class PerformSendActionTask extends BaseDraftTask {
|
|||
shouldDequeueOtherTask(otherTask) {
|
||||
return (
|
||||
otherTask instanceof PerformSendActionTask &&
|
||||
this.draftClientId === otherTask.draftClientId
|
||||
this.headerMessageId === otherTask.headerMessageId
|
||||
)
|
||||
}
|
||||
|
||||
performLocal() {
|
||||
if (!this.draftClientId) {
|
||||
const errMsg = `Attempt to call ${this.constructor.name}.performLocal without a draftClientId`;
|
||||
if (!this.headerMessageId) {
|
||||
const errMsg = `Attempt to call ${this.constructor.name}.performLocal without a headerMessageId`;
|
||||
return Promise.reject(new Error(errMsg));
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
cancel() {
|
||||
const {id: taskId, draftClientId} = this
|
||||
const {id: taskId, headerMessageId} = this
|
||||
clearTimeout(this._sendTimer)
|
||||
Actions.didCancelSendAction({taskId, draftClientId})
|
||||
Actions.didCancelSendAction({taskId, headerMessageId})
|
||||
this._taskResolve(Task.Status.Continue)
|
||||
}
|
||||
|
||||
|
|
|
@ -171,9 +171,9 @@ export default class NylasEnvConstructor {
|
|||
const PackageManager = require('./package-manager');
|
||||
const ThemeManager = require('./theme-manager');
|
||||
const StyleManager = require('./style-manager');
|
||||
const MenuManager = require('./menu-manager').default;
|
||||
const ActionBridge = require('./flux/action-bridge').default;
|
||||
const ActionBridgeCPP = require('./flux/action-bridge-cpp').default;
|
||||
const MenuManager = require('./menu-manager').default;
|
||||
|
||||
const {devMode, benchmarkMode, safeMode, resourcePath, configDirPath, windowType} = this.getLoadSettings();
|
||||
|
||||
|
@ -371,8 +371,13 @@ export default class NylasEnvConstructor {
|
|||
if (event.defaultPrevented) { return; }
|
||||
|
||||
this.lastUncaughtError = error;
|
||||
|
||||
extra.pluginIds = this._findPluginsFromError(error);
|
||||
|
||||
try {
|
||||
extra.pluginIds = this._findPluginsFromError(error);
|
||||
} catch (err) {
|
||||
// can happen when an error is thrown very early
|
||||
extra.pluginIds = [];
|
||||
}
|
||||
|
||||
if (this.inSpecMode()) {
|
||||
jasmine.getEnv().currentSpec.fail(error);
|
||||
|
|
|
@ -6,7 +6,7 @@ import MyComposerButton from '../lib/my-composer-button';
|
|||
describe("MyComposerButton", () => {
|
||||
beforeEach(() => {
|
||||
this.component = ReactTestUtils.renderIntoDocument(
|
||||
<MyComposerButton draftClientId="test" />
|
||||
<MyComposerButton headerMessageId="test" />
|
||||
);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue