Fix drafts

This commit is contained in:
Ben Gotow 2016-01-25 16:36:56 -08:00
parent d008d5f475
commit 22121f9f18
20 changed files with 325 additions and 328 deletions

View file

@ -2,7 +2,6 @@ _ = require 'underscore'
{WorkspaceStore,
MailboxPerspective,
FocusedPerspectiveStore,
DraftCountStore,
DestroyCategoryTask,
Actions} = require 'nylas-exports'
{OutlineViewItem} = require 'nylas-component-kit'
@ -18,7 +17,7 @@ countForItem = (perspective) ->
return 0
isItemSelected = (perspective) ->
(WorkspaceStore.rootSheet() is WorkspaceStore.Sheet.Threads and
(WorkspaceStore.rootSheet() in [WorkspaceStore.Sheet.Threads, WorkspaceStore.Sheet.Drafts] and
FocusedPerspectiveStore.current().isEqual(perspective))
isItemDeleted = (perspective) ->
@ -36,31 +35,29 @@ toggleItemCollapsed = (item) ->
class SidebarItem
@forPerspective: (id, perspective, {children, deletable, name} = {}) ->
children ?= []
@forPerspective: (id, perspective, opts = {}) ->
counterStyle = OutlineViewItem.CounterStyles.Alt if perspective.isInbox()
dataTransferType = 'nylas-thread-ids'
if deletable
onDeleteItem = (item) ->
if opts.deletable
onDeleteItem = (item) ->
# TODO Delete multiple categories at once
return if item.perspective.categories.length > 1
return if item.deleted is true
category = item.perspective.categories[0]
Actions.queueTask(new DestroyCategoryTask({category: category}))
return {
return _.extend({
id: id
name: name ? perspective.name
name: perspective.name
count: countForItem(perspective)
iconName: perspective.iconName
children: children
children: []
perspective: perspective
selected: isItemSelected(perspective)
collapsed: isItemCollapsed(id) ? true
deleted: isItemDeleted(perspective)
counterStyle: counterStyle
dataTransferType: dataTransferType
dataTransferType: 'nylas-thread-ids'
onDelete: onDeleteItem
onToggleCollapsed: toggleItemCollapsed
onDrop: (item, ids) ->
@ -75,7 +72,7 @@ class SidebarItem
onSelect: (item) ->
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
Actions.focusMailboxPerspective(item.perspective)
}
}, opts)
@forCategories: (categories = [], opts = {}) ->
@ -89,30 +86,12 @@ class SidebarItem
id += "-#{opts.name}" if opts.name
@forPerspective(id, perspective, opts)
@forSheet: (id, name, iconName, sheet, count, collapsed, children = []) ->
return {
id,
name,
iconName,
count,
sheet,
children,
collapsed: isItemCollapsed(id) ? true
onToggleCollapsed: toggleItemCollapsed
onSelect: (item) ->
Actions.selectRootSheet(item.sheet)
}
@forDrafts: ({accountId, name, children, collapsed} = {}) ->
id = 'Drafts'
id += "-#{name}" if name
sheet = WorkspaceStore.Sheet.Drafts
iconName = 'drafts.png'
count = if accountId?
DraftCountStore.count(accountId)
else
DraftCountStore.totalCount()
@forSheet(id, name ? id, iconName, sheet, count, collapsed, children)
@forDrafts: (accountIds, opts = {}) ->
perspective = MailboxPerspective.forDrafts(accountIds)
id = "Drafts-#{opts.name}"
opts.onSelect = ->
Actions.focusMailboxPerspective(perspective)
Actions.selectRootSheet(WorkspaceStore.Sheet.Drafts)
@forPerspective(id, perspective, opts)
module.exports = SidebarItem

View file

@ -74,9 +74,8 @@ class SidebarSection
starredItem = SidebarItem.forStarred(_.pluck(accounts, 'id'),
children: accounts.map (acc) -> SidebarItem.forStarred([acc.id], name: acc.label)
)
draftsItem = SidebarItem.forDrafts(
children: accounts.map (acc) ->
SidebarItem.forDrafts(accountId: acc.id, name: acc.label)
draftsItem = SidebarItem.forDrafts(_.pluck(accounts, 'id'),
children: accounts.map (acc) -> SidebarItem.forDrafts([acc.id], name: acc.label)
)
# Order correctly: Inbox, Starred, rest... , Drafts

View file

@ -3,7 +3,6 @@ _ = require 'underscore'
{Actions,
AccountStore,
ThreadCountsStore,
DraftCountStore,
WorkspaceStore,
FocusedPerspectiveStore,
CategoryStore} = require 'nylas-exports'
@ -44,8 +43,8 @@ class SidebarStore extends NylasStore
@listenTo AccountStore, @_updateSections
@listenTo WorkspaceStore, @_updateSections
@listenTo ThreadCountsStore, @_updateSections
@listenTo DraftCountStore, @_updateSections
@listenTo CategoryStore, @_updateSections
@configSubscription = NylasEnv.config.observe(
'core.workspace.showUnreadForAllCategories',
@_updateSections

View file

@ -0,0 +1,48 @@
_ = require 'underscore'
React = require 'react'
classNames = require 'classnames'
{ListTabular, InjectedComponent} = require 'nylas-component-kit'
{timestamp,
subject} = require './formatting-utils'
snippet = (html) =>
return "" unless html and typeof(html) is 'string'
try
@draftSanitizer ?= document.createElement('div')
@draftSanitizer.innerHTML = html[0..400]
text = @draftSanitizer.innerText
text[0..200]
catch
return ""
c1 = new ListTabular.Column
name: "Name"
width: 200
resolver: (draft) =>
<div className="participants">
<InjectedComponent matching={role:"Participants"}
exposedProps={participants: [].concat(draft.to, draft.cc, draft.bcc), clickable: false}/>
</div>
c2 = new ListTabular.Column
name: "Message"
flex: 4
resolver: (draft) =>
attachments = []
if draft.files?.length > 0
attachments = <div className="thread-icon thread-icon-attachment"></div>
<span className="details">
<span className="subject">{subject(draft.subject)}</span>
<span className="snippet">{snippet(draft.body)}</span>
{attachments}
</span>
c3 = new ListTabular.Column
name: "Date"
flex: 1
resolver: (draft) =>
<span className="timestamp">{timestamp(draft.date)}</span>
module.exports =
Wide: [c1, c2, c3]

View file

@ -1,42 +1,44 @@
NylasStore = require 'nylas-store'
Reflux = require 'reflux'
Rx = require 'rx-lite'
_ = require 'underscore'
{Message,
Actions,
AccountStore,
MutableQuerySubscription,
ObservableListDataSource,
FocusedPerspectiveStore,
DatabaseStore} = require 'nylas-exports'
{ListTabular} = require 'nylas-component-kit'
class DraftListStore extends NylasStore
constructor: ->
@listenTo AccountStore, @_onAccountChanged
@listenTo FocusedPerspectiveStore, @_onPerspectiveChanged
@_createListDataSource()
@subscription = new MutableQuerySubscription(@_queryForCurrentAccount(), {asResultSet: true})
$resultSet = Rx.Observable.fromPrivateQuerySubscription('draft-list', @subscription)
dataSource: =>
@_dataSource
@_view = new ObservableListDataSource $resultSet, ({start, end}) =>
@subscription.replaceQuery(@_queryForCurrentAccount().page(start, end))
# Inbound Events
view: =>
@_view
_onPerspectiveChanged: =>
@_createListDataSource()
_queryForCurrentAccount: =>
matchers = [Message.attributes.draft.equal(true)]
account = FocusedPerspectiveStore.current().account
# Internal
if account?
matchers.push(Message.attributes.accountId.equal(account.id))
_createListDataSource: =>
mailboxPerspective = FocusedPerspectiveStore.current()
query = DatabaseStore.findAll(Message)
.include(Message.attributes.body)
.order(Message.attributes.date.descending())
.where(matchers)
.page(0, 1)
if mailboxPerspective.drafts
query = DatabaseStore.findAll(Message)
.include(Message.attributes.body)
.order(Message.attributes.date.descending())
.where(draft: true, accountId: mailboxPerspective.accountIds)
.page(0, 1)
_onAccountChanged: =>
@subscription.replaceQuery(@_queryForCurrentAccount())
subscription = new MutableQuerySubscription(query, {asResultSet: true})
$resultSet = Rx.Observable.fromPrivateQuerySubscription('draft-list', subscription)
@_dataSource = new ObservableListDataSource($resultSet, subscription.replaceRange)
else
@_dataSource = new ListTabular.DataSource.Empty()
@trigger(@)
module.exports = new DraftListStore()

View file

@ -1,13 +1,13 @@
_ = require 'underscore'
React = require 'react'
{ListTabular,
MultiselectList,
InjectedComponent} = require 'nylas-component-kit'
{timestamp, subject} = require './formatting-utils'
{Actions,
FocusedContentStore,
DatabaseStore} = require 'nylas-exports'
FocusedContentStore} = require 'nylas-exports'
{ListTabular,
FluxContainer,
MultiselectList} = require 'nylas-component-kit'
DraftListStore = require './draft-list-store'
DraftListColumns = require './draft-list-columns'
FocusContainer = require './focus-container'
EmptyState = require './empty-state'
class DraftList extends React.Component
@ -15,60 +15,26 @@ class DraftList extends React.Component
@containerRequired: false
componentWillMount: =>
snippet = (html) =>
return "" unless html and typeof(html) is 'string'
try
@draftSanitizer ?= document.createElement('div')
@draftSanitizer.innerHTML = html[0..400]
text = @draftSanitizer.innerText
text[0..200]
catch
return ""
c1 = new ListTabular.Column
name: "Name"
width: 200
resolver: (draft) =>
<div className="participants">
<InjectedComponent matching={role:"Participants"}
exposedProps={participants: [].concat(draft.to, draft.cc, draft.bcc), clickable: false}/>
</div>
c2 = new ListTabular.Column
name: "Message"
flex: 4
resolver: (draft) =>
attachments = []
if draft.files?.length > 0
attachments = <div className="thread-icon thread-icon-attachment"></div>
<span className="details">
<span className="subject">{subject(draft.subject)}</span>
<span className="snippet">{snippet(draft.body)}</span>
{attachments}
</span>
c3 = new ListTabular.Column
name: "Date"
flex: 1
resolver: (draft) =>
<span className="timestamp">{timestamp(draft.date)}</span>
@columns = [c1, c2, c3]
@commands =
'core:remove-from-view': @_onRemoveFromView
render: =>
<MultiselectList
dataStore={DraftListStore}
columns={@columns}
commands={@commands}
onDoubleClick={@_onDoubleClick}
emptyComponent={EmptyState}
itemPropsProvider={ -> {} }
itemHeight={39}
className="draft-list"
collection="draft" />
<FluxContainer
stores=[DraftListStore]
getStateFromStores={ ->
dataSource: DraftListStore.dataSource()
}>
<FocusContainer collection="draft">
<MultiselectList
columns={DraftListColumns.Wide}
commands={@_keymapHandlers()}
onDoubleClick={@_onDoubleClick}
emptyComponent={EmptyState}
itemPropsProvider={ -> {} }
itemHeight={39}
className="draft-list" />
</FocusContainer>
</FluxContainer>
_keymapHandlers: ->
'core:remove-from-view': @_onRemoveFromView
_onDoubleClick: (item) =>
Actions.composePopoutDraft(item.clientId)

View file

@ -69,7 +69,6 @@ class EmptyState extends React.Component
@displayName = 'EmptyState'
@propTypes =
visible: React.PropTypes.bool.isRequired
dataSource: React.PropTypes.object
constructor: (@props) ->
@state =
@ -85,9 +84,7 @@ class EmptyState extends React.Component
@setState(active:true)
shouldComponentUpdate: (nextProps, nextState) ->
# Avoid deep comparison of dataSource, which is a very complex object
return true if nextProps.visible isnt @props.visible
return true if nextProps.dataSource isnt @props.dataSource
return not _.isEqual(nextState, @state)
componentWillUnmount: ->

View file

@ -9,11 +9,10 @@ NylasStore = require 'nylas-store'
FocusedContentStore,
TaskQueueStatusStore,
FocusedPerspectiveStore} = require 'nylas-exports'
{ListTabular} = require 'nylas-component-kit'
ThreadListDataSource = require './thread-list-data-source'
# Public: A mutable text container with undo/redo support and the ability
# to annotate logical regions in the text.
class ThreadListStore extends NylasStore
constructor: ->
@listenTo FocusedPerspectiveStore, @_onPerspectiveChanged
@ -23,18 +22,22 @@ class ThreadListStore extends NylasStore
@_dataSource
createListDataSource: =>
mailboxPerspective = FocusedPerspectiveStore.current()
@_dataSource = new ThreadListDataSource(mailboxPerspective.threads())
@_dataSourceUnlisten?()
@_dataSourceUnlisten = @_dataSource.listen(@_onDataChanged, @)
@_dataSource = null
# Set up a one-time listener to focus an item in the new view
if WorkspaceStore.layoutMode() is 'split'
unlisten = @_dataSource.listen =>
if @_dataSource.loaded()
Actions.setFocus(collection: 'thread', item: @_dataSource.get(0))
unlisten()
threadsSubscription = FocusedPerspectiveStore.current().threads()
if threadsSubscription
@_dataSource = new ThreadListDataSource(threadsSubscription)
@_dataSourceUnlisten = @_dataSource.listen(@_onDataChanged, @)
# Set up a one-time listener to focus an item in the new view
if WorkspaceStore.layoutMode() is 'split'
unlisten = @_dataSource.listen =>
if @_dataSource.loaded()
Actions.setFocus(collection: 'thread', item: @_dataSource.get(0))
unlisten()
else
@_dataSource = new ListTabular.DataSource.Empty()
@trigger(@)
Actions.setFocus(collection: 'thread', item: null)

View file

@ -1,8 +1,10 @@
_ = require 'underscore'
Thread = require '../src/flux/models/thread'
ListDataSource = require '../src/flux/stores/list-data-source'
ListSelection = require '../src/flux/stores/list-selection'
{Thread} = require 'nylas-exports'
{ListTabular} = require 'nylas-component-kit'
ListDataSource = ListTabular.DataSource
ListSelection = ListTabular.Selection
describe "ListSelection", ->
beforeEach ->

View file

@ -40,7 +40,7 @@ describe "FocusedPerspectiveStore", ->
expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory])
describe "_onFocusPerspective", ->
it "should focus the category and trigger when Actions.focusCategory is called", ->
it "should focus the category and trigger", ->
FocusedPerspectiveStore._onFocusPerspective(@userFilter)
expect(FocusedPerspectiveStore.trigger).toHaveBeenCalled()
expect(FocusedPerspectiveStore.current().categories()).toEqual([@userCategory])

View file

@ -2,9 +2,7 @@ _ = require 'underscore'
EventEmitter = require('events').EventEmitter
ListSelection = require './list-selection'
module.exports =
class ListDataSource
constructor: ->
@_emitter = new EventEmitter()
@_cleanedup = false
@ -17,6 +15,8 @@ class ListDataSource
@_emitter.emit('trigger', arg)
listen: (callback, bindContext) ->
unless callback instanceof Function
throw new Error("ListDataSource: You must pass a function to `listen`")
if @_cleanedup is true
throw new Error("ListDataSource: You cannot listen again after removing the last listener. This is an implementation detail.")
@ -56,3 +56,17 @@ class ListDataSource
cleanup: ->
@selection.cleanup()
class EmptyListDataSource extends ListDataSource
loaded: -> true
empty: -> true
get: (idx) -> null
getById: (id) -> null
indexOfId: (id) -> -1
count: -> 0
itemsCurrentlyInViewMatching: (matchFn) -> []
setRetainedRange: ({start, end}) ->
ListDataSource.Empty = EmptyListDataSource
module.exports = ListDataSource

View file

@ -1,14 +1,19 @@
Model = require '../models/model'
DatabaseStore = require './database-store'
_ = require 'underscore'
Model = require '../flux/models/model'
DatabaseStore = require '../flux/stores/database-store'
module.exports =
class ListSelection
constructor: (@_view, @trigger) ->
constructor: (@_view, callback) ->
throw new Error("new ListSelection(): You must provide a view.") unless @_view
@_unlisten = DatabaseStore.listen(@_applyChangeRecord, @)
@_caches = {}
@_items = []
@trigger = =>
@_caches = {}
callback()
cleanup: ->
@_unlisten()
@ -17,7 +22,9 @@ class ListSelection
@_items.length
ids: ->
_.pluck(@_items, 'id')
# ListTabular asks for ids /a lot/. Cache this value and clear it on trigger.
@_caches['ids'] ?= _.pluck(@_items, 'id')
@_caches['ids']
items: -> _.clone(@_items)

View file

@ -0,0 +1,63 @@
_ = require 'underscore'
React = require 'react/addons'
{Utils} = require 'nylas-exports'
class ListTabularItem extends React.Component
@displayName = 'ListTabularItem'
@propTypes =
metrics: React.PropTypes.object
columns: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
item: React.PropTypes.object.isRequired
itemProps: React.PropTypes.object
onSelect: React.PropTypes.func
onClick: React.PropTypes.func
onDoubleClick: React.PropTypes.func
# DO NOT DELETE unless you know what you're doing! This method cuts
# React.Perf.wasted-time from ~300msec to 20msec by doing a deep
# comparison of props before triggering a re-render.
shouldComponentUpdate: (nextProps, nextState) =>
if not Utils.isEqualReact(@props.item, nextProps.item) or @props.columns isnt nextProps.columns
@_columnCache = null
return true
if not Utils.isEqualReact(_.omit(@props, 'item'), _.omit(nextProps, 'item'))
return true
false
render: =>
className = "list-item list-tabular-item #{@props.itemProps?.className}"
props = _.omit(@props.itemProps ? {}, 'className')
# It's expensive to compute the contents of columns (format timestamps, etc.)
# We only do it if the item prop has changed.
@_columnCache ?= @_columns()
<div {...props} className={className} onClick={@_onClick} style={position:'absolute', top: @props.metrics.top, width:'100%', height:@props.metrics.height, overflow: 'hidden'}>
{@_columnCache}
</div>
_columns: =>
names = {}
for column in (@props.columns ? [])
if names[column.name]
console.warn("ListTabular: Columns do not have distinct names, will cause React error! `#{column.name}` twice.")
names[column.name] = true
<div key={column.name}
displayName={column.name}
style={_.pick(column, ['flex', 'width'])}
className="list-column list-column-#{column.name}">
{column.resolver(@props.item, @)}
</div>
_onClick: (event) =>
@props.onSelect?(@props.item, event)
@props.onClick?(@props.item, event)
if @_lastClickTime? and Date.now() - @_lastClickTime < 350
@props.onDoubleClick?(@props.item, event)
@_lastClickTime = Date.now()
module.exports = ListTabularItem

View file

@ -1,69 +1,16 @@
_ = require 'underscore'
React = require 'react/addons'
ScrollRegion = require './scroll-region'
Spinner = require './spinner'
{Utils} = require 'nylas-exports'
ListDataSource = require './list-data-source'
ListSelection = require './list-selection'
ListTabularItem = require './list-tabular-item'
class ListColumn
constructor: ({@name, @resolver, @flex, @width}) ->
class ListTabularItem extends React.Component
@displayName = 'ListTabularItem'
@propTypes =
metrics: React.PropTypes.object
columns: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
item: React.PropTypes.object.isRequired
itemProps: React.PropTypes.object
onSelect: React.PropTypes.func
onClick: React.PropTypes.func
onDoubleClick: React.PropTypes.func
# DO NOT DELETE unless you know what you're doing! This method cuts
# React.Perf.wasted-time from ~300msec to 20msec by doing a deep
# comparison of props before triggering a re-render.
shouldComponentUpdate: (nextProps, nextState) =>
if not Utils.isEqualReact(@props.item, nextProps.item) or @props.columns isnt nextProps.columns
@_columnCache = null
return true
if not Utils.isEqualReact(_.omit(@props, 'item'), _.omit(nextProps, 'item'))
return true
false
render: =>
className = "list-item list-tabular-item #{@props.itemProps?.className}"
props = _.omit(@props.itemProps ? {}, 'className')
# It's expensive to compute the contents of columns (format timestamps, etc.)
# We only do it if the item prop has changed.
@_columnCache ?= @_columns()
<div {...props} className={className} onClick={@_onClick} style={position:'absolute', top: @props.metrics.top, width:'100%', height:@props.metrics.height, overflow: 'hidden'}>
{@_columnCache}
</div>
_columns: =>
names = {}
for column in (@props.columns ? [])
if names[column.name]
console.warn("ListTabular: Columns do not have distinct names, will cause React error! `#{column.name}` twice.")
names[column.name] = true
<div key={column.name}
displayName={column.name}
style={_.pick(column, ['flex', 'width'])}
className="list-column list-column-#{column.name}">
{column.resolver(@props.item, @)}
</div>
_onClick: (event) =>
@props.onSelect?(@props.item, event)
@props.onClick?(@props.item, event)
if @_lastClickTime? and Date.now() - @_lastClickTime < 350
@props.onDoubleClick?(@props.item, event)
@_lastClickTime = Date.now()
class ListTabular extends React.Component
@displayName = 'ListTabular'
@propTypes =
@ -79,16 +26,42 @@ class ListTabular extends React.Component
if not @props.itemHeight
throw new Error("ListTabular: You must provide an itemHeight - raising to avoid divide by zero errors.")
@state =
renderedRangeStart: -1
renderedRangeEnd: -1
@state = @buildStateForRange(start: -1, end: -1)
componentDidMount: =>
window.addEventListener('resize', @onWindowResize, true)
@setupDataSource(@props.dataSource)
@updateRangeState()
componentWillUnmount: =>
window.removeEventListener('resize', @onWindowResize, true)
@_unlisten?()
componentWillReceiveProps: (nextProps) =>
if nextProps.dataSource isnt @props.dataSource
@setupDataSource(nextProps.dataSource)
@setState(@buildStateForRange(dataSource: nextProps.dataSource))
setupDataSource: (dataSource) =>
@_unlisten?()
@_unlisten = dataSource.listen =>
@setState(@buildStateForRange())
buildStateForRange: ({dataSource, start, end} = {}) =>
start ?= @state.renderedRangeStart
end ?= @state.renderedRangeEnd
dataSource ?= @props.dataSource
items = {}
[start..end].forEach (idx) =>
items[idx] = dataSource.get(idx)
renderedRangeStart: start
renderedRangeEnd: end
count: dataSource.count()
loaded: dataSource.loaded()
empty: dataSource.empty()
items: items
componentDidUpdate: (prevProps, prevState) =>
# If our view has been swapped out for an entirely different one,
@ -120,7 +93,7 @@ class ListTabular extends React.Component
# Expand the start/end so that you can advance the keyboard cursor fast and
# we have items to move to and then scroll to.
rangeStart = Math.max(0, rangeStart - 2)
rangeEnd = Math.min(rangeEnd + 2, @props.dataSource.count() + 1)
rangeEnd = Math.min(rangeEnd + 2, @state.count + 1)
# Final sanity check to prevent needless work
return if rangeStart is @state.renderedRangeStart and
@ -132,46 +105,45 @@ class ListTabular extends React.Component
start: rangeStart
end: rangeEnd
@setState
renderedRangeStart: rangeStart
renderedRangeEnd: rangeEnd
@setState(@buildStateForRange(start: rangeStart, end: rangeEnd))
render: =>
innerStyles =
height: @props.dataSource.count() * @props.itemHeight
height: @state.count * @props.itemHeight
<ScrollRegion
ref="container"
onScroll={@onScroll}
tabIndex="-1"
className="list-container list-tabular"
scrollTooltipComponent={@props.scrollTooltipComponent}>
<div className="list-rows" style={innerStyles}>
{@_rows()}
</div>
</ScrollRegion>
emptyElement = false
if @props.emptyComponent
emptyElement = <@props.emptyComponent visible={@state.loaded and @state.empty} />
<div className={@props.className}>
<ScrollRegion
ref="container"
onScroll={@onScroll}
tabIndex="-1"
className="list-container list-tabular"
scrollTooltipComponent={@props.scrollTooltipComponent}>
<div className="list-rows" style={innerStyles}>
{@_rows()}
</div>
</ScrollRegion>
<Spinner visible={!@state.loaded and @state.empty} />
{emptyElement}
</div>
_rows: =>
rows = []
[@state.renderedRangeStart..@state.renderedRangeEnd-1].map (idx) =>
item = @state.items[idx]
return false unless item
for idx in [@state.renderedRangeStart..@state.renderedRangeEnd-1]
item = @props.dataSource.get(idx)
continue unless item
itemProps = {}
if @props.itemPropsProvider
itemProps = @props.itemPropsProvider(item)
rows.push <ListTabularItem key={item.id ? idx}
item={item}
itemProps={itemProps}
metrics={top: idx * @props.itemHeight, height: @props.itemHeight}
columns={@props.columns}
onSelect={@props.onSelect}
onClick={@props.onClick}
onReorder={@props.onReorder}
onDoubleClick={@props.onDoubleClick} />
rows
<ListTabularItem key={item.id ? idx}
item={item}
itemProps={@props.itemPropsProvider?(item) ? {}}
metrics={top: idx * @props.itemHeight, height: @props.itemHeight}
columns={@props.columns}
onSelect={@props.onSelect}
onClick={@props.onClick}
onReorder={@props.onReorder}
onDoubleClick={@props.onDoubleClick} />
# Public: Scroll to the DOM node provided.
#
@ -185,5 +157,7 @@ class ListTabular extends React.Component
ListTabular.Item = ListTabularItem
ListTabular.Column = ListColumn
ListTabular.Selection = ListSelection
ListTabular.DataSource = ListDataSource
module.exports = ListTabular

View file

@ -32,11 +32,7 @@ class MultiselectList extends React.Component
className: React.PropTypes.string.isRequired
columns: React.PropTypes.array.isRequired
itemPropsProvider: React.PropTypes.func.isRequired
itemHeight: React.PropTypes.number.isRequired
scrollTooltipComponent: React.PropTypes.func
emptyComponent: React.PropTypes.func
keymapHandlers: React.PropTypes.object
onDoubleClick: React.PropTypes.func
constructor: (@props) ->
@state = @_getStateFromStores()
@ -103,33 +99,21 @@ class MultiselectList extends React.Component
props = @props.itemPropsProvider(item)
props.className ?= ''
props.className += " " + classNames
'selected': item.id in @state.selectedIds
'selected': item.id in @props.dataSource.selection.ids()
'focused': @state.handler.shouldShowFocus() and item.id is @props.focusedId
'keyboard-cursor': @state.handler.shouldShowKeyboardCursor() and item.id is @props.keyboardCursorId
props['data-item-id'] = item.id
props
emptyElement = []
if @props.emptyComponent
emptyElement = <@props.emptyComponent
visible={@state.loaded and @state.empty}
dataSource={@props.dataSource} />
<KeyCommandsRegion globalHandlers={@_globalKeymapHandlers()} className="multiselect-list">
<div className={className} {...otherProps}>
<ListTabular
ref="list"
columns={@state.computedColumns}
scrollTooltipComponent={@props.scrollTooltipComponent}
dataSource={@props.dataSource}
itemPropsProvider={@itemPropsProvider}
itemHeight={@props.itemHeight}
onSelect={@_onClickItem}
onDoubleClick={@props.onDoubleClick} />
<Spinner visible={!@state.loaded and @state.empty} />
{emptyElement}
</div>
<ListTabular
ref="list"
className={className}
columns={@state.computedColumns}
dataSource={@props.dataSource}
itemPropsProvider={@itemPropsProvider}
onSelect={@_onClickItem}
{...otherProps} />
</KeyCommandsRegion>
else
<div className={className} {...otherProps}>
@ -216,9 +200,6 @@ class MultiselectList extends React.Component
columns: props.columns
computedColumns: computedColumns
layoutMode: layoutMode
selectedIds: props.dataSource.selection.ids()
loaded: props.dataSource.loaded()
empty: props.dataSource.empty()
# Public Methods

View file

@ -1,52 +0,0 @@
_ = require 'underscore'
Rx = require 'rx-lite'
NylasStore = require 'nylas-store'
Actions = require '../actions'
Message = require '../models/message'
Account = require '../models/account'
DatabaseStore = require './database-store'
AccountStore = require './account-store'
FocusedPerspectiveStore = require './focused-perspective-store'
###
Public: The DraftCountStore exposes a simple API for getting the number of
drafts in the user's account. If you plugin needs the number of drafts,
it's more efficient to observe the DraftCountStore than retrieve the value
yourself from the database.
The DraftCountStore is only available in the main window.
###
if not NylasEnv.isMainWindow() and not NylasEnv.inSpecMode() then return
class DraftCountStore extends NylasStore
constructor: ->
@_counts = {}
@_total = 0
@_disposable = Rx.Observable.fromQuery(
DatabaseStore.findAll(Message).where([Message.attributes.draft.equal(true)])
).subscribe(@_onDraftsChanged)
totalCount: ->
@_total
# Public: Returns the number of drafts for the given account
count: (accountOrId)->
return 0 unless accountOrId
accountId = if accountOrId instanceof Account
accountOrId.id
else
accountOrId
@_counts[accountId]
_onDraftsChanged: (drafts) =>
@_total = 0
@_counts = {}
for account in AccountStore.accounts()
@_counts[account.id] = _.where(drafts, accountId: account.id).length
@_total += @_counts[account.id]
@trigger()
module.exports = new DraftCountStore()

View file

@ -33,8 +33,6 @@ class FocusedPerspectiveStore extends NylasStore
_onFocusPerspective: (perspective) =>
return if perspective.isEqual(@_current)
if WorkspaceStore.Sheet.Threads
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
@_setPerspective(perspective)
_onFocusAccounts: (accountsOrIds) =>

View file

@ -5,7 +5,8 @@ Message = require '../models/message'
QuerySubscriptionPool = require '../models/query-subscription-pool'
QuerySubscription = require '../models/query-subscription'
MutableQuerySubscription = require '../models/mutable-query-subscription'
ListDataSource = require './list-data-source'
{ListTabular} = require 'nylas-component-kit'
###
This class takes an observable which vends QueryResultSets and adapts it so that
@ -14,7 +15,7 @@ you can make it the data source of a MultiselectList.
When the MultiselectList is refactored to take an Observable, this class should
go away!
###
class ObservableListDataSource extends ListDataSource
class ObservableListDataSource extends ListTabular.DataSource
constructor: ($resultSetObservable, @_setRetainedRange) ->
super

View file

@ -118,7 +118,6 @@ class NylasExports
@require "ContactStore", 'flux/stores/contact-store'
@require "CategoryStore", 'flux/stores/category-store'
@require "WorkspaceStore", 'flux/stores/workspace-store'
@require "DraftCountStore", 'flux/stores/draft-count-store'
@require "FileUploadStore", 'flux/stores/file-upload-store'
@require "MailRulesStore", 'flux/stores/mail-rules-store'
@require "ThreadCountsStore", 'flux/stores/thread-counts-store'

View file

@ -18,6 +18,9 @@ class MailboxPerspective
@forNothing: ->
new EmptyMailboxPerspective()
@forDrafts: (accountsOrIds) ->
new DraftsMailboxPerspective(accountsOrIds)
@forCategory: (category) ->
return @forNothing() unless category
new CategoryMailboxPerspective([category])
@ -111,6 +114,20 @@ class SearchMailboxPerspective extends MailboxPerspective
false
class DraftsMailboxPerspective extends MailboxPerspective
constructor: (@accountIds) ->
super(@accountIds)
@name = "Drafts"
@iconName = "drafts.png"
@drafts = true # The DraftListStore looks for this
@
threads: =>
null
canApplyToThreads: =>
false
class StarredMailboxPerspective extends MailboxPerspective
constructor: (@accountIds) ->
super(@accountIds)