Mailspring/internal_packages/thread-list/lib/thread-list.cjsx
Ben Gotow 8f2211f6a0 feat(threads): List improvements and message collapsing
Summary:
This diff uses the new ?expanded=true threads request to fetch threads and the messages inside them at the same time. The messages from this endpoint don't contain bodies. Message bodies have been moved to a new "secondary attribute" type, which can be optionally requested when making queries. This allows us to 1) quickly fetch messages without worrying about MBs of JSON, 2) update messages without updating their bodies, and 3) avoid calls to /messages?thread_id=123. The new message store fetches just the items it wants to display in expanded mode, and we'll show snippets for the rest.

Fix up forwarded message

Approach: Thread.messageMetadata

join approach WIP

join approach complete

"" || null = null. OMG.

Make spinner a bit smarter, use code delays and not css delays

Search suggestion store should only show first 10 matches

Msg collapsing, refactored msg store that will fetch individual messages that are marked as expanded, set loaded = true when it's all done

Test Plan: Tests coming soon. The query refactoring here broke a lot of tests...

Reviewers: evan

Reviewed By: evan

Differential Revision: https://review.inboxapp.com/D1345
2015-03-25 12:41:48 -07:00

150 lines
4.7 KiB
CoffeeScript

_ = require 'underscore-plus'
React = require 'react'
{ListTabular, Spinner} = require 'ui-components'
{timestamp, subject} = require './formatting-utils'
{Actions,
Utils,
ThreadStore,
WorkspaceStore,
NamespaceStore} = require 'inbox-exports'
ThreadListParticipants = require './thread-list-participants'
module.exports =
ThreadList = React.createClass
displayName: 'ThreadList'
getInitialState: ->
@_getStateFromStores()
componentDidMount: ->
@thread_store_unsubscribe = ThreadStore.listen @_onChange
@thread_unsubscriber = atom.commands.add '.thread-list', {
'thread-list:star-thread': => @_onStarThread()
}
@body_unsubscriber = atom.commands.add 'body', {
'application:previous-item': => @_onShiftSelectedIndex(-1)
'application:next-item': => @_onShiftSelectedIndex(1)
'application:focus-item': => @_onFocusSelectedIndex()
'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
}
componentWillUnmount: ->
@thread_store_unsubscribe()
@thread_unsubscriber.dispose()
@body_unsubscriber.dispose()
render: ->
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: ->
myEmail = NamespaceStore.current()?.emailAddress
labelComponents = (thread) =>
for label in @state.threadLabelComponents
LabelComponent = label.view
<LabelComponent thread={thread} />
lastMessageType = (thread) ->
msgs = thread.messageMetadata
return 'unknown' unless msgs and msgs instanceof Array and msgs.length > 0
msg = msgs[msgs.length - 1]
if thread.unread
return 'unread'
else if msg.from[0].email isnt myEmail
return 'other'
else if Utils.isForwardedMessage(msg)
return 'forwarded'
else
return 'replied'
c1 = new ListTabular.Column
name: ""
resolver: (thread) ->
<div className="thread-icon thread-icon-#{lastMessageType(thread)}"></div>
c2 = new ListTabular.Column
name: "Name"
flex: 1
resolver: (thread) =>
<ThreadListParticipants thread={thread} />
c3 = new ListTabular.Column
name: "Message"
flex: 4
resolver: (thread) ->
attachments = []
if thread.hasTagId('attachment')
attachments = <div className="thread-icon thread-icon-attachment"></div>
<span className="details">
<span className="subject">{subject(thread.subject)}</span>
<span className="snippet">{thread.snippet}</span>
{attachments}
</span>
c4 = new ListTabular.Column
name: "Date"
resolver: (thread) ->
<span className="timestamp">{timestamp(thread.lastMessageTimestamp)}</span>
[c1, c2, c3, c4]
_onFocusSelectedIndex: ->
Actions.selectThreadId(@state.selectedId)
_onShiftSelectedIndex: (delta) ->
item = _.find @state.items, (thread) => thread.id == @state.selectedId
index = if item then @state.items.indexOf(item) else -1
index = Math.max(0, Math.min(index + delta, @state.items.length-1))
Actions.selectThreadId(@state.items[index].id)
_onStarThread: ->
thread = ThreadStore.selectedThread()
thread.toggleStar() if thread
_onReply: ->
return unless @state.selectedId? and @_actionInVisualScope()
Actions.composeReply(threadId: @state.selectedId)
_onReplyAll: ->
return unless @state.selectedId? and @_actionInVisualScope()
Actions.composeReplyAll(threadId: @state.selectedId)
_onForward: ->
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())
_getStateFromStores: ->
ready: not ThreadStore.itemsLoading()
items: ThreadStore.items()
columns: @_computeColumns()
selectedId: ThreadStore.selectedId()