mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-19 01:27:02 +08:00
feat(archive): archive now pops back to thread list
Summary: add spec for message toolbar items add thread list spec Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://review.inboxapp.com/D1344
This commit is contained in:
parent
79ad424849
commit
dc5941f3f1
14 changed files with 194 additions and 46 deletions
|
@ -30,9 +30,19 @@ AccountSidebarStore = Reflux.createStore
|
|||
|
||||
_registerListeners: ->
|
||||
@listenTo Actions.selectTagId, @_onSelectTagId
|
||||
@listenTo Actions.searchQueryCommitted, @_onSearchQueryCommitted
|
||||
@listenTo DatabaseStore, @_onDataChanged
|
||||
@listenTo NamespaceStore, @_onNamespaceChanged
|
||||
|
||||
_onSearchQueryCommitted: (query) ->
|
||||
if query? and query isnt ""
|
||||
@_oldSelectedId = @_selectedId
|
||||
@_selectedId = "search"
|
||||
else
|
||||
@_selectedId = @_oldSelectedId if @_oldSelectedId
|
||||
|
||||
@trigger(@)
|
||||
|
||||
_populate: ->
|
||||
namespace = NamespaceStore.current()
|
||||
return unless namespace
|
||||
|
@ -116,6 +126,7 @@ AccountSidebarStore = Reflux.createStore
|
|||
@_populateDraftsCount()
|
||||
|
||||
_onSelectTagId: (tagId) ->
|
||||
Actions.searchQueryCommitted('') if @_selectedId is "search"
|
||||
@_selectedId = tagId
|
||||
@trigger(@)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
React = require 'react'
|
||||
{Actions, ThreadStore, Utils} = require 'inbox-exports'
|
||||
{Actions, ThreadStore, Utils, WorkspaceStore} = require 'inbox-exports'
|
||||
{RetinaImg} = require 'ui-components'
|
||||
|
||||
# Note: These always have a thread, but only sometimes get a
|
||||
|
@ -46,7 +46,7 @@ ForwardButton = React.createClass
|
|||
|
||||
ArchiveButton = React.createClass
|
||||
render: ->
|
||||
<button className="btn btn-toolbar"
|
||||
<button className="btn btn-toolbar btn-archive"
|
||||
data-tooltip="Archive"
|
||||
onClick={@_onArchive}>
|
||||
<RetinaImg name="toolbar-archive.png" />
|
||||
|
@ -54,13 +54,15 @@ ArchiveButton = React.createClass
|
|||
|
||||
_onArchive: (e) ->
|
||||
return unless Utils.nodeIsVisible(e.currentTarget)
|
||||
# Calling archive() sends an Actions.queueTask with an archive task
|
||||
# TODO Turn into an Action
|
||||
ThreadStore.selectedThread().archive()
|
||||
if WorkspaceStore.selectedLayoutMode() is "list"
|
||||
Actions.archiveCurrentThread()
|
||||
else if WorkspaceStore.selectedLayoutMode() is "split"
|
||||
Actions.archiveAndNext()
|
||||
e.stopPropagation()
|
||||
|
||||
|
||||
module.exports = React.createClass
|
||||
module.exports =
|
||||
MessageToolbarItems = React.createClass
|
||||
getInitialState: ->
|
||||
threadIsSelected: ThreadStore.selectedId()?
|
||||
|
||||
|
@ -70,7 +72,7 @@ module.exports = React.createClass
|
|||
"hidden": !@state.threadIsSelected
|
||||
|
||||
<div className={classes}>
|
||||
<ArchiveButton />
|
||||
<ArchiveButton ref="archiveButton" />
|
||||
</div>
|
||||
|
||||
componentDidMount: ->
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
React = require 'react/addons'
|
||||
ReactTestUtils = React.addons.TestUtils
|
||||
MessageToolbarItems = require "../lib/message-toolbar-items.cjsx"
|
||||
{WorkspaceStore, Actions} = require 'inbox-exports'
|
||||
|
||||
describe "MessageToolbarItems", ->
|
||||
beforeEach ->
|
||||
@toolbarItems = ReactTestUtils.renderIntoDocument(<MessageToolbarItems />)
|
||||
@archiveButton = @toolbarItems.refs["archiveButton"]
|
||||
spyOn(Actions, "archiveAndNext")
|
||||
spyOn(Actions, "archiveCurrentThread")
|
||||
|
||||
it "renders the archive button", ->
|
||||
btns = ReactTestUtils.scryRenderedDOMComponentsWithClass(@toolbarItems, "btn-archive")
|
||||
expect(btns.length).toBe 1
|
||||
|
||||
it "archives and next in split mode", ->
|
||||
spyOn(WorkspaceStore, "selectedLayoutMode").andReturn "split"
|
||||
ReactTestUtils.Simulate.click(@archiveButton.getDOMNode())
|
||||
expect(Actions.archiveCurrentThread).not.toHaveBeenCalled()
|
||||
expect(Actions.archiveAndNext).toHaveBeenCalled()
|
||||
|
||||
it "archives in list mode", ->
|
||||
spyOn(WorkspaceStore, "selectedLayoutMode").andReturn "list"
|
||||
ReactTestUtils.Simulate.click(@archiveButton.getDOMNode())
|
||||
expect(Actions.archiveCurrentThread).toHaveBeenCalled()
|
||||
expect(Actions.archiveAndNext).not.toHaveBeenCalled()
|
|
@ -307,7 +307,7 @@
|
|||
///////////////////////////////
|
||||
.sidebar-thread-participants {
|
||||
padding: @spacing-standard;
|
||||
order: 10;
|
||||
order: 2;
|
||||
flex-shrink: 0;
|
||||
|
||||
.other-contact {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
border:none;
|
||||
|
||||
input {
|
||||
padding-top: 3.5px;
|
||||
padding-left:30px;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
|
@ -66,6 +67,10 @@
|
|||
|
||||
&.showing-suggestions {
|
||||
.suggestions { display: inherit; }
|
||||
.clear {
|
||||
color: @input-accessory-color;
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
&.showing-query {
|
||||
.clear { display: inherit; }
|
||||
|
@ -82,8 +87,8 @@
|
|||
|
||||
&.clear {
|
||||
position: absolute;
|
||||
top: floor(40px - 26px)/2;
|
||||
color: @input-accessory-color;
|
||||
top: floor(40px - 26px)/2 - 1px;
|
||||
color: @input-cancel-color;
|
||||
right: @padding-base-horizontal;
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.internal-sidebar {
|
||||
padding: @spacing-standard;
|
||||
padding-bottom: 0;
|
||||
order: 2;
|
||||
order: 4;
|
||||
flex-shrink: 0;
|
||||
|
||||
a{text-decoration: none}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
_ = require 'underscore-plus'
|
||||
React = require 'react'
|
||||
{ListTabular} = require 'ui-components'
|
||||
{ListTabular, Spinner} = require 'ui-components'
|
||||
{timestamp, subject} = require './formatting-utils'
|
||||
{Actions, ThreadStore, ComponentRegistry} = require 'inbox-exports'
|
||||
{Actions,
|
||||
ThreadStore,
|
||||
WorkspaceStore,
|
||||
ComponentRegistry} = require 'inbox-exports'
|
||||
|
||||
module.exports =
|
||||
ThreadList = React.createClass
|
||||
|
@ -23,8 +26,9 @@ ThreadList = React.createClass
|
|||
'application:previous-item': => @_onShiftSelectedIndex(-1)
|
||||
'application:next-item': => @_onShiftSelectedIndex(1)
|
||||
'application:focus-item': => @_onFocusSelectedIndex()
|
||||
'application:remove-item': @_onArchiveSelected
|
||||
'application:remove-and-previous': @_onArchiveAndPrevious
|
||||
'application:remove-item': @_onArchiveCurrentThread
|
||||
'application:remove-and-previous': -> Actions.archiveAndPrevious()
|
||||
'application:remove-and-next': -> Actions.archiveAndNext()
|
||||
'application:reply': @_onReply
|
||||
'application:reply-all': @_onReplyAll
|
||||
'application:forward': @_onForward
|
||||
|
@ -36,13 +40,15 @@ ThreadList = React.createClass
|
|||
@body_unsubscriber.dispose()
|
||||
|
||||
render: ->
|
||||
<div className="thread-list">
|
||||
classes = React.addons.classSet("thread-list": true, "ready": @state.ready)
|
||||
<div className={classes}>
|
||||
<ListTabular
|
||||
columns={@state.columns}
|
||||
items={@state.items}
|
||||
itemClassProvider={ (item) -> if item.isUnread() then 'unread' else '' }
|
||||
selectedId={@state.selectedId}
|
||||
onSelect={ (item) -> Actions.selectThreadId(item.id) } />
|
||||
<Spinner visible={!@state.ready} />
|
||||
</div>
|
||||
|
||||
_computeColumns: ->
|
||||
|
@ -106,34 +112,38 @@ ThreadList = React.createClass
|
|||
index = Math.max(0, Math.min(index + delta, @state.items.length-1))
|
||||
Actions.selectThreadId(@state.items[index].id)
|
||||
|
||||
_onArchiveSelected: ->
|
||||
thread = ThreadStore.selectedThread()
|
||||
thread.archive() if thread
|
||||
|
||||
_onStarThread: ->
|
||||
thread = ThreadStore.selectedThread()
|
||||
thread.toggleStar() if thread
|
||||
|
||||
_onReply: ->
|
||||
return unless @state.selectedId?
|
||||
return unless @state.selectedId? and @_actionInVisualScope()
|
||||
Actions.composeReply(threadId: @state.selectedId)
|
||||
|
||||
_onReplyAll: ->
|
||||
return unless @state.selectedId?
|
||||
return unless @state.selectedId? and @_actionInVisualScope()
|
||||
Actions.composeReplyAll(threadId: @state.selectedId)
|
||||
|
||||
_onForward: ->
|
||||
return unless @state.selectedId?
|
||||
return unless @state.selectedId? and @_actionInVisualScope()
|
||||
Actions.composeForward(threadId: @state.selectedId)
|
||||
|
||||
|
||||
_actionInVisualScope: ->
|
||||
if WorkspaceStore.selectedLayoutMode() is "list"
|
||||
WorkspaceStore.sheet().type is "Thread"
|
||||
else true
|
||||
|
||||
_onArchiveCurrentThread: ->
|
||||
if WorkspaceStore.selectedLayoutMode() is "list"
|
||||
Actions.archiveCurrentThread()
|
||||
else if WorkspaceStore.selectedLayoutMode() is "split"
|
||||
Actions.archiveAndNext()
|
||||
|
||||
_onChange: ->
|
||||
@setState(@_getStateFromStores())
|
||||
|
||||
_onArchiveAndPrevious: ->
|
||||
@_onArchiveSelected()
|
||||
@_onShiftSelectedIndex(-1)
|
||||
|
||||
_getStateFromStores: ->
|
||||
ready: not ThreadStore.itemsLoading()
|
||||
items: ThreadStore.items()
|
||||
columns: @_computeColumns()
|
||||
selectedId: ThreadStore.selectedId()
|
||||
|
|
|
@ -10,6 +10,7 @@ ReactTestUtils = _.extend ReactTestUtils, require "jasmine-react-helpers"
|
|||
Namespace,
|
||||
ThreadStore,
|
||||
DatabaseStore,
|
||||
WorkspaceStore,
|
||||
InboxTestUtils,
|
||||
NamespaceStore,
|
||||
ComponentRegistry} = require "inbox-exports"
|
||||
|
@ -212,6 +213,9 @@ describe "ThreadList", ->
|
|||
spyOn(ThreadStore, "_onNamespaceChanged")
|
||||
spyOn(DatabaseStore, "findAll").andCallFake ->
|
||||
new Promise (resolve, reject) -> resolve(test_threads())
|
||||
spyOn(Actions, "archiveCurrentThread")
|
||||
spyOn(Actions, "archiveAndNext")
|
||||
spyOn(Actions, "archiveAndPrevious")
|
||||
ReactTestUtils.spyOnClass(ThreadList, "_computeColumns").andReturn(columns)
|
||||
|
||||
ThreadStore._resetInstanceVars()
|
||||
|
@ -240,6 +244,47 @@ describe "ThreadList", ->
|
|||
items = ReactTestUtils.scryRenderedComponentsWithType(@thread_list, ListTabular.Item)
|
||||
expect(items.length).toBe 0
|
||||
|
||||
describe "when the workspace is in list mode", ->
|
||||
beforeEach ->
|
||||
spyOn(WorkspaceStore, "selectedLayoutMode").andReturn "list"
|
||||
@thread_list.setState selectedId: "t111"
|
||||
|
||||
it "archives in list mode", ->
|
||||
@thread_list._onArchiveCurrentThread()
|
||||
expect(Actions.archiveCurrentThread).toHaveBeenCalled()
|
||||
expect(Actions.archiveAndNext).not.toHaveBeenCalled()
|
||||
|
||||
it "allows reply only when the sheet type is 'Thread'", ->
|
||||
spyOn(WorkspaceStore, "sheet").andCallFake -> {type: "Thread"}
|
||||
spyOn(Actions, "composeReply")
|
||||
@thread_list._onReply()
|
||||
expect(Actions.composeReply).toHaveBeenCalled()
|
||||
expect(@thread_list._actionInVisualScope()).toBe true
|
||||
|
||||
it "doesn't reply only when the sheet type isnt 'Thread'", ->
|
||||
spyOn(WorkspaceStore, "sheet").andCallFake -> {type: "Root"}
|
||||
spyOn(Actions, "composeReply")
|
||||
@thread_list._onReply()
|
||||
expect(Actions.composeReply).not.toHaveBeenCalled()
|
||||
expect(@thread_list._actionInVisualScope()).toBe false
|
||||
|
||||
describe "when the workspace is in split mode", ->
|
||||
beforeEach ->
|
||||
spyOn(WorkspaceStore, "selectedLayoutMode").andReturn "split"
|
||||
@thread_list.setState selectedId: "t111"
|
||||
|
||||
it "archives and next in split mode", ->
|
||||
@thread_list._onArchiveCurrentThread()
|
||||
expect(Actions.archiveCurrentThread).not.toHaveBeenCalled()
|
||||
expect(Actions.archiveAndNext).toHaveBeenCalled()
|
||||
|
||||
it "allows reply and reply-all regardless of sheet type", ->
|
||||
spyOn(WorkspaceStore, "sheet").andCallFake -> {type: "anything"}
|
||||
spyOn(Actions, "composeReply")
|
||||
@thread_list._onReply()
|
||||
expect(Actions.composeReply).toHaveBeenCalled()
|
||||
expect(@thread_list._actionInVisualScope()).toBe true
|
||||
|
||||
describe "Populated thread list", ->
|
||||
beforeEach ->
|
||||
ThreadStore._items = test_threads()
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
'j' : 'application:next-item' # Gmail
|
||||
'down' : 'application:next-item' # Mac mail
|
||||
']' : 'application:remove-and-previous' # Gmail
|
||||
'[' : 'application:remove-item' # Gmail
|
||||
'[' : 'application:remove-and-next' # Gmail
|
||||
'e' : 'application:remove-item' # Gmail
|
||||
'delete' : 'application:remove-item' # Mac mail
|
||||
'backspace': 'application:remove-item' # Outlook
|
||||
|
@ -20,6 +20,7 @@
|
|||
'/' : 'application:focus-search' # Gmail
|
||||
|
||||
'r' : 'application:reply' # Gmail
|
||||
'R' : 'application:reply-all' # Edgehill
|
||||
'a' : 'application:reply-all' # Gmail
|
||||
'f' : 'application:forward' # Gmail
|
||||
|
||||
|
|
|
@ -70,6 +70,10 @@ windowActions = [
|
|||
"sendDraft",
|
||||
"destroyDraft",
|
||||
|
||||
"archiveAndPrevious",
|
||||
"archiveCurrentThread",
|
||||
"archiveAndNext",
|
||||
|
||||
# Actions for Search
|
||||
"searchQueryChanged",
|
||||
"searchQueryCommitted",
|
||||
|
|
|
@ -232,8 +232,8 @@ class InboxAPI
|
|||
# API abstraction should not need to know about threads and calendars.
|
||||
# They're still here because of their dependency in
|
||||
# _postLaunchStartStreaming
|
||||
getThreads: (namespaceId, params) ->
|
||||
@getCollection(namespaceId, 'threads', params)
|
||||
getThreads: (namespaceId, params, requestOptions={}) ->
|
||||
@getCollection(namespaceId, 'threads', params, requestOptions)
|
||||
|
||||
getCalendars: (namespaceId) ->
|
||||
@getCollection(namespaceId, 'calendars', {})
|
||||
|
|
|
@ -61,11 +61,6 @@ class Thread extends Model
|
|||
isStarred: ->
|
||||
@tagIds().indexOf('starred') != -1
|
||||
|
||||
markAsRead: ->
|
||||
MarkThreadReadTask = require '../tasks/mark-thread-read'
|
||||
task = new MarkThreadReadTask(@id)
|
||||
Actions.queueTask(task)
|
||||
|
||||
star: ->
|
||||
@addRemoveTags(['starred'], [])
|
||||
|
||||
|
@ -78,13 +73,6 @@ class Thread extends Model
|
|||
else
|
||||
@star()
|
||||
|
||||
archive: ->
|
||||
Actions.postNotification({message: "Archived thread", type: 'success'})
|
||||
@addRemoveTags(['archive'], ['inbox'])
|
||||
|
||||
unarchive: ->
|
||||
@addRemoveTags(['inbox'], ['archive'])
|
||||
|
||||
addRemoveTags: (tagIdsToAdd, tagIdsToRemove) ->
|
||||
# start web change, which will dispatch more actions
|
||||
AddRemoveTagsTask = require '../tasks/add-remove-tags'
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
Reflux = require 'reflux'
|
||||
DatabaseStore = require './database-store'
|
||||
NamespaceStore = require './namespace-store'
|
||||
AddRemoveTagsTask = require '../tasks/add-remove-tags'
|
||||
MarkThreadReadTask = require '../tasks/mark-thread-read'
|
||||
Actions = require '../actions'
|
||||
Thread = require '../models/thread'
|
||||
_ = require 'underscore-plus'
|
||||
|
@ -11,6 +13,9 @@ ThreadStore = Reflux.createStore
|
|||
|
||||
@listenTo Actions.selectThreadId, @_onSelectThreadId
|
||||
@listenTo Actions.selectTagId, @_onSelectTagId
|
||||
@listenTo Actions.archiveAndPrevious, @_onArchiveAndPrevious
|
||||
@listenTo Actions.archiveCurrentThread, @_onArchiveCurrentThread
|
||||
@listenTo Actions.archiveAndNext, @_onArchiveAndNext
|
||||
@listenTo Actions.searchQueryCommitted, @_onSearchCommitted
|
||||
@listenTo DatabaseStore, @_onDataChanged
|
||||
@listenTo NamespaceStore, -> @_onNamespaceChanged()
|
||||
|
@ -23,6 +28,9 @@ ThreadStore = Reflux.createStore
|
|||
@_namespaceId = null
|
||||
@_tagId = null
|
||||
@_searchQuery = null
|
||||
@_itemsLoading = false
|
||||
|
||||
itemsLoading: -> @_itemsLoading
|
||||
|
||||
fetchFromCache: ->
|
||||
return unless @_namespaceId
|
||||
|
@ -56,16 +64,26 @@ ThreadStore = Reflux.createStore
|
|||
newSelectedId = null
|
||||
Actions.selectThreadId(newSelectedId)
|
||||
|
||||
@_itemsLoading = false
|
||||
@trigger()
|
||||
|
||||
fetchFromAPI: ->
|
||||
return unless @_namespaceId
|
||||
@_itemsLoading = true
|
||||
if @_searchQuery
|
||||
atom.inbox.getThreadsForSearch @_namespaceId, @_searchQuery, (items) =>
|
||||
@_items = items
|
||||
@_itemsLoading = false
|
||||
@trigger()
|
||||
else
|
||||
atom.inbox.getThreads(@_namespaceId, {tag: @_tagId})
|
||||
success = =>
|
||||
@_itemsLoading = false
|
||||
@trigger()
|
||||
error = =>
|
||||
@_itemsLoading = false
|
||||
@trigger()
|
||||
atom.inbox.getThreads(@_namespaceId, {tag: @_tagId}, {success: success, error: error})
|
||||
@trigger()
|
||||
|
||||
# Inbound Events
|
||||
|
||||
|
@ -83,6 +101,8 @@ ThreadStore = Reflux.createStore
|
|||
@fetchFromCache()
|
||||
|
||||
_onSearchCommitted: (query) ->
|
||||
Actions.selectThreadId(null)
|
||||
|
||||
if query.length > 0
|
||||
@_searchQuery = query
|
||||
@_items = []
|
||||
|
@ -93,7 +113,6 @@ ThreadStore = Reflux.createStore
|
|||
@fetchFromCache()
|
||||
|
||||
@_lastQuery = query
|
||||
Actions.selectThreadId(null)
|
||||
@fetchFromAPI()
|
||||
|
||||
_onSelectTagId: (id) ->
|
||||
|
@ -108,10 +127,45 @@ ThreadStore = Reflux.createStore
|
|||
|
||||
thread = @selectedThread()
|
||||
if thread && thread.isUnread()
|
||||
thread.markAsRead()
|
||||
Actions.queueTask(new MarkThreadReadTask(thread.id))
|
||||
|
||||
@trigger()
|
||||
|
||||
_onArchiveCurrentThread: ({silent}={}) ->
|
||||
thread = @selectedThread()
|
||||
return unless thread
|
||||
@_archive(thread.id)
|
||||
@_selectedId = null
|
||||
if not silent
|
||||
@trigger()
|
||||
Actions.popSheet()
|
||||
Actions.selectThreadId(null)
|
||||
|
||||
_archive: (threadId) ->
|
||||
Actions.postNotification({message: "Archived thread", type: 'success'})
|
||||
task = new AddRemoveTagsTask(threadId, ['archive'], ['inbox'])
|
||||
Actions.queueTask(task)
|
||||
|
||||
_threadOffsetFromCurrentBy: (offset=0) ->
|
||||
thread = @selectedThread()
|
||||
index = @_items.indexOf(thread)
|
||||
return null if index is -1
|
||||
index += offset
|
||||
index = Math.min(Math.max(index, 0), @_items.length - 1)
|
||||
return @_items[index]
|
||||
|
||||
_onArchiveAndPrevious: ->
|
||||
return unless @_selectedId
|
||||
newSelectedId = @_threadOffsetFromCurrentBy(-1)?.id
|
||||
@_onArchiveCurrentThread(silent: true)
|
||||
Actions.selectThreadId(newSelectedId)
|
||||
|
||||
_onArchiveAndNext: ->
|
||||
return unless @_selectedId
|
||||
newSelectedId = @_threadOffsetFromCurrentBy(1)?.id
|
||||
@_onArchiveCurrentThread(silent: true)
|
||||
Actions.selectThreadId(newSelectedId)
|
||||
|
||||
# Accessing Data
|
||||
|
||||
selectedTagId: ->
|
||||
|
|
|
@ -261,6 +261,7 @@
|
|||
|
||||
@input-accessory-color-hover: @light-blue;
|
||||
@input-accessory-color: @cool-gray;
|
||||
@input-cancel-color: @red;
|
||||
|
||||
//** Text color for `<input>`s
|
||||
@input-color: @gray;
|
||||
|
|
Loading…
Add table
Reference in a new issue