mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-14 01:04:18 +08:00
feat(menu): add right click menu
Summary: Adds a right click menu to the thread list Moving to folders and labels #TODO Test Plan: TODO Reviewers: bengotow, juan Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2609
This commit is contained in:
parent
95aba56979
commit
fb4e806afe
3 changed files with 193 additions and 13 deletions
162
internal_packages/thread-list/lib/thread-list-context-menu.es6
Normal file
162
internal_packages/thread-list/lib/thread-list-context-menu.es6
Normal file
|
@ -0,0 +1,162 @@
|
|||
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});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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.composeReplyAll({threadId: this.threadIds[0], popout: true});
|
||||
},
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
forwardItem() {
|
||||
if (this.threadIds.length !== 1) { return null }
|
||||
return {
|
||||
label: "Forward",
|
||||
click: () => {
|
||||
Actions.composeForward({threadId: this.threadIds[0], popout: true});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
archiveItem() {
|
||||
if (!FocusedPerspectiveStore.current().canArchiveThreads()) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
label: "Archive",
|
||||
click: () => {
|
||||
const tasks = TaskFactory.tasksForArchiving({
|
||||
threads: this.threads,
|
||||
fromPerspective: FocusedPerspectiveStore.current(),
|
||||
})
|
||||
Actions.queueTasks(tasks)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
trashItem() {
|
||||
if (!FocusedPerspectiveStore.current().canTrashThreads()) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
label: "Trash",
|
||||
click: () => {
|
||||
const tasks = TaskFactory.tasksForMovingToTrash({
|
||||
threads: this.threads,
|
||||
fromPerspective: FocusedPerspectiveStore.current(),
|
||||
})
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ classNames = require 'classnames'
|
|||
TaskFactory,
|
||||
ChangeUnreadTask,
|
||||
WorkspaceStore,
|
||||
AccountStore,
|
||||
CategoryStore,
|
||||
FocusedContentStore,
|
||||
FocusedPerspectiveStore} = require 'nylas-exports'
|
||||
|
@ -20,6 +19,7 @@ ThreadListScrollTooltip = require './thread-list-scroll-tooltip'
|
|||
ThreadListStore = require './thread-list-store'
|
||||
FocusContainer = require './focus-container'
|
||||
EmptyState = require './empty-state'
|
||||
ThreadListContextMenu = require './thread-list-context-menu'
|
||||
|
||||
|
||||
class ThreadList extends React.Component
|
||||
|
@ -36,10 +36,12 @@ class ThreadList extends React.Component
|
|||
|
||||
componentDidMount: =>
|
||||
window.addEventListener('resize', @_onResize, true)
|
||||
React.findDOMNode(@).addEventListener('contextmenu', @_onShowContextMenu)
|
||||
@_onResize()
|
||||
|
||||
componentWillUnmount: =>
|
||||
window.removeEventListener('resize', @_onResize, true)
|
||||
React.findDOMNode(@).removeEventListener('contextmenu', @_onShowContextMenu)
|
||||
|
||||
_shift: ({offset, afterRunning}) =>
|
||||
dataSource = ThreadListStore.dataSource()
|
||||
|
@ -99,25 +101,37 @@ class ThreadList extends React.Component
|
|||
className: classNames
|
||||
'unread': item.unread
|
||||
|
||||
_onDragStart: (event) =>
|
||||
_threadMouseManipulateData: (event) ->
|
||||
itemThreadId = @refs.list.itemIdAtPoint(event.clientX, event.clientY)
|
||||
unless itemThreadId
|
||||
event.preventDefault()
|
||||
return
|
||||
return null
|
||||
|
||||
dataSource = ThreadListStore.dataSource()
|
||||
if itemThreadId in dataSource.selection.ids()
|
||||
dragThreadIds = dataSource.selection.ids()
|
||||
dragAccountIds = _.uniq(_.pluck(dataSource.selection.items(), 'accountId'))
|
||||
threadIds = dataSource.selection.ids()
|
||||
accountIds = _.uniq(_.pluck(dataSource.selection.items(), 'accountId'))
|
||||
else
|
||||
dragThreadIds = [itemThreadId]
|
||||
dragAccountIds = [dataSource.getById(itemThreadId).accountId]
|
||||
threadIds = [itemThreadId]
|
||||
accountIds = [dataSource.getById(itemThreadId).accountId]
|
||||
|
||||
dragData = {
|
||||
accountIds: dragAccountIds,
|
||||
threadIds: dragThreadIds
|
||||
return {
|
||||
accountIds: accountIds,
|
||||
threadIds: threadIds
|
||||
}
|
||||
|
||||
_onShowContextMenu: (event) =>
|
||||
data = @_threadMouseManipulateData(event)
|
||||
if not data
|
||||
event.preventDefault()
|
||||
return
|
||||
(new ThreadListContextMenu(data)).displayMenu()
|
||||
|
||||
_onDragStart: (event) =>
|
||||
dragData = @_threadMouseManipulateData(event)
|
||||
if not dragData
|
||||
event.preventDefault()
|
||||
return
|
||||
|
||||
event.dataTransfer.effectAllowed = "move"
|
||||
event.dataTransfer.dragEffect = "move"
|
||||
|
||||
|
|
|
@ -279,10 +279,14 @@ class DraftStore
|
|||
queries.message = DatabaseStore.find(Message, messageId)
|
||||
queries.message.include(Message.attributes.body)
|
||||
else
|
||||
queries.message = DatabaseStore.findBy(Message, {threadId: threadId ? thread.id}).order(Message.attributes.date.descending()).limit(1)
|
||||
queries.message.include(Message.attributes.body)
|
||||
queries.message = @_lastMessageFromThreadId(threadId ? thread.id)
|
||||
return queries
|
||||
|
||||
_lastMessageFromThreadId: (threadId) ->
|
||||
query = DatabaseStore.findBy(Message, {threadId: threadId ? thread.id}).order(Message.attributes.date.descending()).limit(1)
|
||||
query.include(Message.attributes.body)
|
||||
return query
|
||||
|
||||
_constructDraft: ({attributes, thread}) =>
|
||||
account = AccountStore.accountForId(thread.accountId)
|
||||
throw new Error("Cannot find #{thread.accountId}") unless account
|
||||
|
|
Loading…
Reference in a new issue