Mailspring/internal_packages/thread-list/lib/thread-list-context-menu.es6
Ben Gotow c265cf0dfa fix(drafts): Formalize draft factory, add reply "behaviors" #1722
Summary:
This diff implements a behavior change described in https://github.com/nylas/N1/issues/1722.

Reply buttons should prefer to focus an existing draft in reply to the same message, if one is pristine, altering it as necessary to switch between reply / reply-all. If no pristine reply is already there, it creates one.

Reply keyboard shortcuts should do the same, but more strictly - the shortcuts should switch between reply / reply-all for an existing draft regardless of whether it's pristine.

This diff also cleans up the DraftStore and moves all the draft creation itself to a new DraftFactory object. This makes it much easier to see what's going on in the DraftStore, and I also refactored away the "newMessageWithContext" method, which was breaking the logic for Reply vs Forward between a bunch of different helper methods and was hard to follow.

Test Plan: They're all wrecked. Will fix after concept is greenlighted

Reviewers: evan, juan

Reviewed By: juan

Differential Revision: https://phab.nylas.com/D2776
2016-03-22 15:47:51 -07:00

175 lines
4 KiB
JavaScript

import _ from 'underscore'
import {
Thread,
Actions,
Message,
TaskFactory,
DatabaseStore,
FocusedPerspectiveStore,
} from 'nylas-exports'
export default class ThreadListContextMenu {
constructor({threadIds = [], accountIds = []}) {
this.threadIds = threadIds
this.accountIds = accountIds
}
menuItemTemplate() {
return DatabaseStore.modelify(Thread, this.threadIds)
.then((threads) => {
this.threads = threads;
return Promise.all([
this.replyItem(),
this.replyAllItem(),
this.forwardItem(),
{type: 'separator'},
this.archiveItem(),
this.trashItem(),
this.markAsReadItem(),
this.starItem(),
// this.moveToOrLabelItem(),
// {type: 'separator'},
// this.extensionItems(),
])
}).then((menuItems) => {
return _.filter(_.compact(menuItems), (item, index) => {
if ((index === 0 || index === menuItems.length - 1) && item.type === "separator") {
return false
}
return true
});
});
}
replyItem() {
if (this.threadIds.length !== 1) { return null }
return {
label: "Reply",
click: () => {
Actions.composeReply({
threadId: this.threadIds[0],
popout: true,
type: 'reply',
behavior: 'prefer-existing-if-pristine',
});
},
}
}
replyAllItem() {
if (this.threadIds.length !== 1) { return null }
DatabaseStore.findBy(Message, {threadId: this.threadIds[0]})
.order(Message.attributes.date.descending())
.limit(1)
.then((message) => {
if (message && message.canReplyAll()) {
return {
label: "Reply All",
click: () => {
Actions.composeReply({
threadId: this.threadIds[0],
popout: true,
type: 'reply-all',
behavior: 'prefer-existing-if-pristine',
});
},
}
}
return null
})
}
forwardItem() {
if (this.threadIds.length !== 1) { return null }
return {
label: "Forward",
click: () => {
Actions.composeForward({threadId: this.threadIds[0], popout: true});
},
}
}
archiveItem() {
const perspective = FocusedPerspectiveStore.current()
const canArchiveThreads = perspective.canArchiveThreads(this.threads)
if (!canArchiveThreads) {
return null
}
return {
label: "Archive",
click: () => {
const tasks = TaskFactory.tasksForArchiving({
threads: this.threads,
})
Actions.queueTasks(tasks)
},
}
}
trashItem() {
const perspective = FocusedPerspectiveStore.current()
const canTrashThreads = perspective.canTrashThreads(this.threads)
if (!canTrashThreads) {
return null
}
return {
label: "Trash",
click: () => {
const tasks = TaskFactory.tasksForMovingToTrash({
threads: this.threads,
})
Actions.queueTasks(tasks)
},
}
}
markAsReadItem() {
const unread = _.every(this.threads, (t) => {
return _.isMatch(t, {unread: false})
});
const dir = unread ? "Unread" : "Read"
return {
label: `Mark as ${dir}`,
click: () => {
const task = TaskFactory.taskForInvertingUnread({
threads: this.threads,
})
Actions.queueTask(task)
},
}
}
starItem() {
const starred = _.every(this.threads, (t) => {
return _.isMatch(t, {starred: false})
});
let dir = ""
let star = "Star"
if (!starred) {
dir = "Remove "
star = (this.threadIds.length > 1) ? "Stars" : "Star"
}
return {
label: `${dir}${star}`,
click: () => {
const task = TaskFactory.taskForInvertingStarred({
threads: this.threads,
})
Actions.queueTask(task)
},
}
}
displayMenu() {
const {remote} = require('electron')
this.menuItemTemplate().then((template) => {
remote.Menu.buildFromTemplate(template)
.popup(remote.getCurrentWindow());
});
}
}