mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-11 01:54:40 +08:00
More account sidebar refactor + sections for unified inbox
- Refactors some of the old code which was 💩
- Makes SidbarSection a factory for different types of items for the
OutlineView
- Decided not to create a OutlineViewItem.Model class since the only
purpose it would serve would be to validate getters for props or for
documentation, both of which are already done via React.PropTypes.
- Adds sections when looking at unified inbox and integrates with new
mailbox perspecitve interface
- Updates OutlineViewItem a bit + styles
- Tests missing
This commit is contained in:
parent
fbbc325a84
commit
d592474073
18 changed files with 462 additions and 677 deletions
|
@ -1,105 +0,0 @@
|
|||
{WorkspaceStore,
|
||||
FocusedPerspectiveStore,
|
||||
ThreadCountsStore,
|
||||
DraftCountStore,
|
||||
DestroyCategoryTask,
|
||||
Actions} = require 'nylas-exports'
|
||||
_ = require 'underscore'
|
||||
{OutlineViewItem} = require 'nylas-component-kit'
|
||||
|
||||
|
||||
class MailboxPerspectiveSidebarItem
|
||||
|
||||
constructor: (@mailboxPerspective, @shortenedName, @children = []) ->
|
||||
category = @mailboxPerspective.categories()[0]
|
||||
|
||||
@id = category?.id ? @mailboxPerspective.name
|
||||
@name = @shortenedName ? @mailboxPerspective.name
|
||||
@iconName = @mailboxPerspective.iconName
|
||||
@dataTransferType = 'nylas-thread-ids'
|
||||
@counterStyle = OutlineViewItem.CounterStyles.Alt if @mailboxPerspective.isInbox()
|
||||
|
||||
# Sidenote: I think treating the sidebar items as dumb bundles of data is a
|
||||
# good idea. `count` /shouldn't/ be a function since if it's value changes,
|
||||
# it wouldn't trigger a refresh or anything. It'd just be confusing if it
|
||||
# could change. But making these all classes makes it feel like you should
|
||||
# call these methods externally.
|
||||
#
|
||||
# Might be good to make a factory that returns OutlineViewItemModels instead
|
||||
# of having classes here. eg: AccountSidebar.itemForPerspective(p) returns
|
||||
# { count: X, isSelected: false, isDeleted: true}...
|
||||
#
|
||||
@count = @_count()
|
||||
@selected = @_isSelected()
|
||||
@deleted = @_isDeleted()
|
||||
@collapsed = @_isCollapsed()
|
||||
|
||||
@
|
||||
|
||||
_count: =>
|
||||
unreadCountEnabled = NylasEnv.config.get('core.workspace.showUnreadForAllCategories')
|
||||
if @mailboxPerspective.isInbox() or unreadCountEnabled
|
||||
return @mailboxPerspective.threadUnreadCount()
|
||||
return 0
|
||||
|
||||
_isSelected: =>
|
||||
(WorkspaceStore.rootSheet() is WorkspaceStore.Sheet.Threads and
|
||||
FocusedPerspectiveStore.current().isEqual(@mailboxPerspective))
|
||||
|
||||
_isDeleted: =>
|
||||
_.any @mailboxPerspective.categories(), (c) -> c.isDeleted
|
||||
|
||||
_isCollapsed: =>
|
||||
key = "core.accountSidebarCollapsed.#{@id}"
|
||||
NylasEnv.config.get(key)
|
||||
|
||||
onToggleCollapsed: =>
|
||||
return unless @children.length > 0
|
||||
key = "core.accountSidebarCollapsed.#{@id}"
|
||||
@collapsed = not @_isCollapsed()
|
||||
NylasEnv.config.set(key, @collapsed)
|
||||
|
||||
onDelete: =>
|
||||
return if @category?.isDeleted is true
|
||||
Actions.queueTask(new DestroyCategoryTask({category: @category}))
|
||||
|
||||
onDrop: (ids) =>
|
||||
return unless ids
|
||||
@mailboxPerspective.applyToThreads(ids)
|
||||
|
||||
shouldAcceptDrop: (event) =>
|
||||
perspective = @mailboxPerspective
|
||||
return false unless perspective
|
||||
return false if perspective.isEqual(FocusedPerspectiveStore.current())
|
||||
return false unless perspective.canApplyToThreads()
|
||||
@dataTransferType in event.dataTransfer.types
|
||||
|
||||
onSelect: =>
|
||||
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
|
||||
Actions.focusMailboxPerspective(@mailboxPerspective)
|
||||
|
||||
|
||||
class SheetSidebarItem
|
||||
|
||||
constructor: (@name, @iconName, @sheet) ->
|
||||
@id = @sheet?.id ? @name
|
||||
@selected = WorkspaceStore.rootSheet().id is @id
|
||||
|
||||
onSelect: =>
|
||||
Actions.selectRootSheet(@sheet)
|
||||
|
||||
|
||||
class DraftListSidebarItem extends SheetSidebarItem
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
count: ->
|
||||
DraftCountStore.count()
|
||||
|
||||
|
||||
module.exports = {
|
||||
MailboxPerspectiveSidebarItem
|
||||
SheetSidebarItem
|
||||
DraftListSidebarItem
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{Actions, SyncbackCategoryTask, DestroyCategoryTask} = require 'nylas-exports'
|
||||
|
||||
class AccountSidebarSection
|
||||
|
||||
constructor: ({@title, @iconName, @items} = {}) ->
|
||||
|
||||
|
||||
class CategorySidebarSection extends AccountSidebarSection
|
||||
|
||||
constructor: ({@title, @iconName, @account, @items} = {}) ->
|
||||
|
||||
onCreateItem: (displayName) =>
|
||||
return unless @account
|
||||
CategoryClass = @account.categoryClass()
|
||||
category = new CategoryClass
|
||||
displayName: displayName
|
||||
accountId: @account.id
|
||||
Actions.queueTask(new SyncbackCategoryTask({category}))
|
||||
|
||||
module.exports = {
|
||||
AccountSidebarSection
|
||||
CategorySidebarSection
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
NylasStore = require 'nylas-store'
|
||||
_ = require 'underscore'
|
||||
{DatabaseStore,
|
||||
AccountStore,
|
||||
ThreadCountsStore,
|
||||
DraftCountStore,
|
||||
WorkspaceStore,
|
||||
MailboxPerspective,
|
||||
FocusedPerspectiveStore,
|
||||
DestroyCategoryTask,
|
||||
CategoryHelpers,
|
||||
CategoryStore} = require 'nylas-exports'
|
||||
|
||||
{AccountSidebarSection,
|
||||
CategorySidebarSection} = require './account-sidebar-sections'
|
||||
{DraftListSidebarItem,
|
||||
MailboxPerspectiveSidebarItem} = require './account-sidebar-items'
|
||||
|
||||
|
||||
Sections = {
|
||||
"Accounts"
|
||||
"Mailboxes"
|
||||
"Categories"
|
||||
}
|
||||
|
||||
class AccountSidebarStore extends NylasStore
|
||||
|
||||
constructor: ->
|
||||
@_sections = {}
|
||||
@_account = AccountStore.accounts()[0] # TODO Just to prevent a crash at launch
|
||||
# @_account = FocusedPerspectiveStore.current().account
|
||||
@_registerListeners()
|
||||
@_updateAccountsSection()
|
||||
@_updateSections()
|
||||
|
||||
currentAccount: ->
|
||||
@_account
|
||||
|
||||
accountsSection: ->
|
||||
@_sections[Sections.Accounts]
|
||||
|
||||
mailboxesSection: ->
|
||||
@_sections[Sections.Mailboxes]
|
||||
|
||||
categoriesSection: ->
|
||||
@_sections[Sections.Categories]
|
||||
|
||||
_registerListeners: ->
|
||||
@listenTo ThreadCountsStore, @_updateSections
|
||||
@listenTo DraftCountStore, @_updateSections
|
||||
@listenTo CategoryStore, @_updateSections
|
||||
@listenTo FocusedPerspectiveStore, @_onPerspectiveChanged
|
||||
@configSubscription = NylasEnv.config.observe(
|
||||
'core.workspace.showUnreadForAllCategories',
|
||||
@_updateSections
|
||||
)
|
||||
@configSubscription = NylasEnv.config.observe(
|
||||
'core.accountSidebarCollapsed',
|
||||
@_updateSections
|
||||
)
|
||||
|
||||
# TODO this needs to change
|
||||
_onPerspectiveChanged: =>
|
||||
account = FocusedPerspectiveStore.current().account
|
||||
if account?.id isnt @_account?.id
|
||||
@_account = account
|
||||
@_updateSections()
|
||||
@trigger()
|
||||
|
||||
_onAccountsChanged: =>
|
||||
@_updateAccountsSection()
|
||||
@trigger()
|
||||
|
||||
_updateSections: =>
|
||||
@_updateAccountsSection()
|
||||
@_updateMailboxesSection()
|
||||
@_updateCategoriesSection()
|
||||
@trigger()
|
||||
|
||||
_updateAccountsSection: =>
|
||||
@_sections[Sections.Accounts] = new AccountSidebarSection(
|
||||
title: 'Accounts'
|
||||
items: []
|
||||
)
|
||||
|
||||
_updateMailboxesSection: =>
|
||||
return unless @_account
|
||||
|
||||
# Drafts are displayed via a `DraftListSidebarItem`
|
||||
standardCategories = CategoryStore.standardCategories(@_account)
|
||||
items = _.reject(standardCategories, (cat) => cat.name is "drafts")
|
||||
.map (cat) =>
|
||||
new MailboxPerspectiveSidebarItem(MailboxPerspective.forCategory(cat))
|
||||
|
||||
starredItem = new MailboxPerspectiveSidebarItem(MailboxPerspective.forStarred([@_account.id]))
|
||||
draftsItem = new DraftListSidebarItem('Drafts', 'drafts.png', WorkspaceStore.Sheet.Drafts)
|
||||
|
||||
# Order correctly: Inbox, Starred, rest... , Drafts
|
||||
items.splice(1, 0, starredItem)
|
||||
items.push(draftsItem)
|
||||
|
||||
@_sections[Sections.Mailboxes] = new AccountSidebarSection(
|
||||
title: 'Mailboxes'
|
||||
items: items
|
||||
)
|
||||
|
||||
_updateCategoriesSection: =>
|
||||
return unless @_account
|
||||
|
||||
# Compute hierarchy for user categories using known "path" separators
|
||||
# NOTE: This code uses the fact that userCategoryItems is a sorted set, eg:
|
||||
#
|
||||
# Inbox
|
||||
# Inbox.FolderA
|
||||
# Inbox.FolderA.FolderB
|
||||
# Inbox.FolderB
|
||||
#
|
||||
items = []
|
||||
seenItems = {}
|
||||
for category in CategoryStore.userCategories(@_account)
|
||||
# https://regex101.com/r/jK8cC2/1
|
||||
itemKey = category.displayName.replace(/[./\\]/g, '/')
|
||||
perspective = MailboxPerspective.forCategory(@_account, category)
|
||||
|
||||
parent = null
|
||||
parentComponents = itemKey.split('/')
|
||||
for i in [parentComponents.length..1] by -1
|
||||
parentKey = parentComponents[0...i].join('/')
|
||||
parent = seenItems[parentKey]
|
||||
break if parent
|
||||
|
||||
if parent
|
||||
itemDisplayName = category.displayName.substr(parentKey.length+1)
|
||||
item = new MailboxPerspectiveSidebarItem(perspective, itemDisplayName)
|
||||
parent.children.push(item)
|
||||
else
|
||||
item = new MailboxPerspectiveSidebarItem(perspective)
|
||||
items.push(item)
|
||||
seenItems[itemKey] = item
|
||||
|
||||
@_sections[Sections.Categories] = new CategorySidebarSection(
|
||||
title: CategoryHelpers.categoryLabel(@_account)
|
||||
iconName: CategoryHelpers.categoryIconName(@_account)
|
||||
account: @_account
|
||||
items: items
|
||||
)
|
||||
|
||||
|
||||
module.exports = new AccountSidebarStore()
|
|
@ -1,7 +1,7 @@
|
|||
_ = require 'underscore'
|
||||
React = require 'react'
|
||||
{OutlineView, ScrollRegion} = require 'nylas-component-kit'
|
||||
AccountSidebarStore = require '../account-sidebar-store'
|
||||
SidebarStore = require '../sidebar-store'
|
||||
|
||||
|
||||
class AccountSidebar extends React.Component
|
||||
|
@ -17,7 +17,7 @@ class AccountSidebar extends React.Component
|
|||
|
||||
componentDidMount: =>
|
||||
@unsubscribers = []
|
||||
@unsubscribers.push AccountSidebarStore.listen @_onStoreChange
|
||||
@unsubscribers.push SidebarStore.listen @_onStoreChange
|
||||
|
||||
componentWillUnmount: =>
|
||||
unsubscribe() for unsubscribe in @unsubscribers
|
||||
|
@ -26,19 +26,21 @@ class AccountSidebar extends React.Component
|
|||
@setState @_getStateFromStores()
|
||||
|
||||
_getStateFromStores: =>
|
||||
sections: [
|
||||
AccountSidebarStore.mailboxesSection()
|
||||
AccountSidebarStore.categoriesSection()
|
||||
]
|
||||
standardSection: SidebarStore.standardSection()
|
||||
userSections: SidebarStore.userSections()
|
||||
|
||||
_renderSections: =>
|
||||
@state.sections.map (section) =>
|
||||
<OutlineView key={section.label} {...section} />
|
||||
_renderUserSections: (sections) =>
|
||||
sections.map (section) =>
|
||||
<OutlineView key={section.title} {...section} />
|
||||
|
||||
render: =>
|
||||
standardSection = @state.standardSection
|
||||
userSections = @state.userSections
|
||||
|
||||
<ScrollRegion className="account-sidebar" >
|
||||
<div className="account-sidebar-sections">
|
||||
{@_renderSections()}
|
||||
<OutlineView {...standardSection} />
|
||||
{@_renderUserSections(userSections)}
|
||||
</div>
|
||||
</ScrollRegion>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
React = require 'react'
|
||||
AccountSidebarStore = require './account-sidebar-store'
|
||||
SidebarStore = require '../sidebar-store'
|
||||
{Actions, AccountStore} = require("nylas-exports")
|
||||
{RetinaImg} = require 'nylas-component-kit'
|
||||
crypto = require 'crypto'
|
||||
|
@ -129,6 +129,6 @@ class AccountSwitcher extends React.Component
|
|||
|
||||
_getStateFromStores: =>
|
||||
accounts: AccountStore.accounts()
|
||||
account: AccountSidebarStore.currentAccount()
|
||||
account: SidebarStore.currentAccount()
|
||||
|
||||
module.exports = AccountSwitcher
|
||||
|
|
11
internal_packages/account-sidebar/lib/sidebar-actions.coffee
Normal file
11
internal_packages/account-sidebar/lib/sidebar-actions.coffee
Normal file
|
@ -0,0 +1,11 @@
|
|||
Reflux = require 'reflux'
|
||||
|
||||
Actions = [
|
||||
'selectAccount'
|
||||
]
|
||||
|
||||
for key in Actions
|
||||
Actions[key] = Reflux.createAction(name)
|
||||
Actions[key].sync = true
|
||||
|
||||
module.exports = Actions
|
114
internal_packages/account-sidebar/lib/sidebar-item.coffee
Normal file
114
internal_packages/account-sidebar/lib/sidebar-item.coffee
Normal file
|
@ -0,0 +1,114 @@
|
|||
_ = require 'underscore'
|
||||
{WorkspaceStore,
|
||||
MailboxPerspective,
|
||||
FocusedPerspectiveStore,
|
||||
DraftCountStore,
|
||||
DestroyCategoryTask,
|
||||
Actions} = require 'nylas-exports'
|
||||
{OutlineViewItem} = require 'nylas-component-kit'
|
||||
|
||||
|
||||
idForCategories = (categories) ->
|
||||
categories.map((cat) -> cat.id).join('-')
|
||||
|
||||
countForItem = (perspective) ->
|
||||
unreadCountEnabled = NylasEnv.config.get('core.workspace.showUnreadForAllCategories')
|
||||
if perspective.isInbox() or unreadCountEnabled
|
||||
return perspective.threadUnreadCount()
|
||||
return 0
|
||||
|
||||
isItemSelected = (perspective) ->
|
||||
(WorkspaceStore.rootSheet() is WorkspaceStore.Sheet.Threads and
|
||||
FocusedPerspectiveStore.current().isEqual(perspective))
|
||||
|
||||
isItemDeleted = (perspective) ->
|
||||
_.any perspective.categories(), (c) -> c.isDeleted
|
||||
|
||||
isItemCollapsed = (id) ->
|
||||
key = "core.accountSidebarCollapsed.#{id}"
|
||||
NylasEnv.config.get(key)
|
||||
|
||||
|
||||
class SidebarItem
|
||||
|
||||
@forPerspective: (id, perspective, {children, deletable, name} = {}) ->
|
||||
children ?= []
|
||||
counterStyle = OutlineViewItem.CounterStyles.Alt if perspective.isInbox()
|
||||
dataTransferType = 'nylas-thread-ids'
|
||||
|
||||
if 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 {
|
||||
id: id
|
||||
name: name ? perspective.name
|
||||
count: countForItem(perspective)
|
||||
iconName: perspective.iconName
|
||||
children: children
|
||||
perspective: perspective
|
||||
selected: isItemSelected(perspective)
|
||||
collapsed: isItemCollapsed(id)
|
||||
deleted: isItemDeleted(perspective)
|
||||
counterStyle: counterStyle
|
||||
dataTransferType: dataTransferType
|
||||
onDelete: onDeleteItem
|
||||
onToggleCollapsed: (item) ->
|
||||
return unless item.children.length > 0
|
||||
key = "core.accountSidebarCollapsed.#{item.id}"
|
||||
NylasEnv.config.set(key, not item.collapsed)
|
||||
onDrop: (item, ids) ->
|
||||
return unless ids
|
||||
item.perspective.applyToThreads(ids)
|
||||
shouldAcceptDrop: (item, event) ->
|
||||
perspective = item.perspective
|
||||
return false unless perspective
|
||||
return false if perspective.isEqual(FocusedPerspectiveStore.current())
|
||||
return false unless perspective.canApplyToThreads()
|
||||
item.dataTransferType in event.dataTransfer.types
|
||||
onSelect: (item) ->
|
||||
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
|
||||
Actions.focusMailboxPerspective(item.perspective)
|
||||
}
|
||||
|
||||
|
||||
@forCategories: (categories = [], opts = {}) ->
|
||||
id = idForCategories(categories)
|
||||
perspective = MailboxPerspective.forCategories(categories)
|
||||
@forPerspective(id, perspective, opts)
|
||||
|
||||
@forStarred: (accountIds, opts = {}) ->
|
||||
perspective = MailboxPerspective.forStarred(accountIds)
|
||||
id = 'Starred'
|
||||
id += "-#{opts.name}" if opts.name
|
||||
@forPerspective(id, perspective, opts)
|
||||
|
||||
@forSheet: (name, iconName, sheet, count, children = []) ->
|
||||
id = sheet?.id ? name
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
iconName,
|
||||
count,
|
||||
sheet,
|
||||
children,
|
||||
onSelect: (item) ->
|
||||
Actions.selectRootSheet(item.sheet)
|
||||
}
|
||||
|
||||
@forDrafts: ({accountId, name, children} = {}) ->
|
||||
sheet = WorkspaceStore.Sheet.Drafts
|
||||
iconName = 'drafts.png'
|
||||
name ?= 'Drafts'
|
||||
count = if accountId?
|
||||
DraftCountStore.count(accountId)
|
||||
else
|
||||
DraftCountStore.totalCount()
|
||||
@forSheet(name, iconName, sheet, count)
|
||||
|
||||
|
||||
module.exports = SidebarItem
|
134
internal_packages/account-sidebar/lib/sidebar-section.coffee
Normal file
134
internal_packages/account-sidebar/lib/sidebar-section.coffee
Normal file
|
@ -0,0 +1,134 @@
|
|||
_ = require 'underscore'
|
||||
{Actions,
|
||||
SyncbackCategoryTask,
|
||||
DestroyCategoryTask,
|
||||
CategoryHelpers,
|
||||
CategoryStore,
|
||||
Category} = require 'nylas-exports'
|
||||
SidebarItem = require './sidebar-item'
|
||||
|
||||
|
||||
class SidebarSection
|
||||
|
||||
@empty: (title)->
|
||||
return {
|
||||
title,
|
||||
items: []
|
||||
}
|
||||
|
||||
@standardSectionForAccount: (account) ->
|
||||
cats = CategoryStore.standardCategories(account)
|
||||
items = _
|
||||
.reject(cats, (cat) -> cat.name is 'drafts')
|
||||
.map (cat) => SidebarItem.forCategories([cat])
|
||||
|
||||
starredItem = SidebarItem.forStarred([account.id])
|
||||
draftsItem = SidebarItem.forDrafts(accountId: account.id)
|
||||
|
||||
# Order correctly: Inbox, Starred, rest... , Drafts
|
||||
items.splice(1, 0, starredItem)
|
||||
items.push(draftsItem)
|
||||
|
||||
return {
|
||||
title: 'Mailboxes'
|
||||
items: items
|
||||
}
|
||||
|
||||
@standardSectionForAccounts: (accounts) ->
|
||||
return @empty('Mailboxes') if not accounts or accounts.length is 0
|
||||
return @empty('Mailboxes') if CategoryStore.categories().length is 0
|
||||
return @standardSectionForAccount(accounts[0]) if accounts.length is 1
|
||||
|
||||
# TODO Decide standard items for the unified case
|
||||
inboxItem = SidebarItem.forCategories(
|
||||
(accounts.map (acc)-> CategoryStore.getStandardCategory(acc, 'inbox')),
|
||||
children: accounts.map (acc) ->
|
||||
cat = CategoryStore.getStandardCategory(acc, 'inbox')
|
||||
SidebarItem.forCategories([cat], name: acc.label)
|
||||
)
|
||||
sentItem = SidebarItem.forCategories(
|
||||
(accounts.map (acc)-> CategoryStore.getStandardCategory(acc, 'sent')),
|
||||
children: accounts.map (acc) ->
|
||||
cat = CategoryStore.getStandardCategory(acc, 'sent')
|
||||
SidebarItem.forCategories([cat], name: acc.label)
|
||||
)
|
||||
archiveItem = SidebarItem.forCategories(
|
||||
(accounts.map (acc)-> CategoryStore.getArchiveCategory(acc)),
|
||||
children: accounts.map (acc) ->
|
||||
cat = CategoryStore.getArchiveCategory(acc)
|
||||
SidebarItem.forCategories([cat], name: acc.label)
|
||||
)
|
||||
trashItem = SidebarItem.forCategories(
|
||||
(accounts.map (acc)-> CategoryStore.getTrashCategory(acc)),
|
||||
children: accounts.map (acc) ->
|
||||
cat = CategoryStore.getTrashCategory(acc)
|
||||
SidebarItem.forCategories([cat], name: acc.label)
|
||||
)
|
||||
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)
|
||||
)
|
||||
|
||||
items = [
|
||||
inboxItem
|
||||
starredItem
|
||||
sentItem
|
||||
archiveItem
|
||||
trashItem
|
||||
draftsItem
|
||||
]
|
||||
|
||||
return {
|
||||
title: 'Mailboxes'
|
||||
items: items
|
||||
}
|
||||
|
||||
|
||||
@forUserCategories: (account) ->
|
||||
return unless account
|
||||
# Compute hierarchy for user categories using known "path" separators
|
||||
# NOTE: This code uses the fact that userCategoryItems is a sorted set, eg:
|
||||
#
|
||||
# Inbox
|
||||
# Inbox.FolderA
|
||||
# Inbox.FolderA.FolderB
|
||||
# Inbox.FolderB
|
||||
#
|
||||
items = []
|
||||
seenItems = {}
|
||||
for category in CategoryStore.userCategories(account)
|
||||
# https://regex101.com/r/jK8cC2/1
|
||||
itemKey = category.displayName.replace(/[./\\]/g, '/')
|
||||
|
||||
parent = null
|
||||
parentComponents = itemKey.split('/')
|
||||
for i in [parentComponents.length..1] by -1
|
||||
parentKey = parentComponents[0...i].join('/')
|
||||
parent = seenItems[parentKey]
|
||||
break if parent
|
||||
|
||||
if parent
|
||||
itemDisplayName = category.displayName.substr(parentKey.length+1)
|
||||
item = SidebarItem.forCategories([category], name: itemDisplayName)
|
||||
parent.children.push(item)
|
||||
else
|
||||
item = SidebarItem.forCategories([category])
|
||||
items.push(item)
|
||||
seenItems[itemKey] = item
|
||||
|
||||
return {
|
||||
title: CategoryHelpers.categoryLabel(account)
|
||||
iconName: CategoryHelpers.categoryIconName(account)
|
||||
items: items
|
||||
onCreateItem: (displayName) ->
|
||||
category = new Category
|
||||
displayName: displayName
|
||||
accountId: account.id
|
||||
Actions.queueTask(new SyncbackCategoryTask({category}))
|
||||
}
|
||||
|
||||
|
||||
module.exports = SidebarSection
|
68
internal_packages/account-sidebar/lib/sidebar-store.coffee
Normal file
68
internal_packages/account-sidebar/lib/sidebar-store.coffee
Normal file
|
@ -0,0 +1,68 @@
|
|||
NylasStore = require 'nylas-store'
|
||||
_ = require 'underscore'
|
||||
{DatabaseStore,
|
||||
AccountStore,
|
||||
ThreadCountsStore,
|
||||
DraftCountStore,
|
||||
WorkspaceStore,
|
||||
MailboxPerspective,
|
||||
FocusedPerspectiveStore,
|
||||
DestroyCategoryTask,
|
||||
CategoryHelpers,
|
||||
CategoryStore} = require 'nylas-exports'
|
||||
|
||||
SidebarSection = require './sidebar-section'
|
||||
SidebarActions = require './sidebar-actions'
|
||||
|
||||
Sections = {
|
||||
"Standard",
|
||||
"User"
|
||||
}
|
||||
|
||||
class SidebarStore extends NylasStore
|
||||
|
||||
constructor: ->
|
||||
@_sections = {}
|
||||
# @_account = AccountStore.accounts()[0]
|
||||
@_account = FocusedPerspectiveStore.current().account
|
||||
@_registerListeners()
|
||||
@_updateSections()
|
||||
|
||||
standardSection: ->
|
||||
@_sections[Sections.Standard]
|
||||
|
||||
userSections: ->
|
||||
@_sections[Sections.User]
|
||||
|
||||
_registerListeners: ->
|
||||
@listenTo SidebarActions.selectAccount, @_onAccountSelected
|
||||
@listenTo AccountStore, @_updateSections
|
||||
@listenTo WorkspaceStore, @_updateSections
|
||||
@listenTo ThreadCountsStore, @_updateSections
|
||||
@listenTo DraftCountStore, @_updateSections
|
||||
@listenTo CategoryStore, @_updateSections
|
||||
@listenTo FocusedPerspectiveStore, @_updateSections
|
||||
@configSubscription = NylasEnv.config.observe(
|
||||
'core.workspace.showUnreadForAllCategories',
|
||||
@_updateSections
|
||||
)
|
||||
@configSubscription = NylasEnv.config.observe(
|
||||
'core.accountSidebarCollapsed',
|
||||
@_updateSections
|
||||
)
|
||||
return
|
||||
|
||||
_onAccountSelected: (account) =>
|
||||
if @_account isnt account
|
||||
@_account = account
|
||||
@_updateSections()
|
||||
|
||||
_updateSections: =>
|
||||
accounts = if @_account? then [@_account] else AccountStore.accounts()
|
||||
@_sections[Sections.Standard] = SidebarSection.standardSectionForAccounts(accounts)
|
||||
@_sections[Sections.User] = accounts.map (acc) ->
|
||||
SidebarSection.forUserCategories(acc)
|
||||
@trigger()
|
||||
|
||||
|
||||
module.exports = new SidebarStore()
|
|
@ -1,283 +0,0 @@
|
|||
AccountSidebarStore = require '../lib/account-sidebar-store'
|
||||
{Folder, WorkspaceStore, CategoryStore, AccountStore} = require 'nylas-exports'
|
||||
NylasEnv.testOrganizationUnit = 'folder'
|
||||
|
||||
describe "AccountSidebarStore", ->
|
||||
describe "sections", ->
|
||||
it "should return the correct output", ->
|
||||
|
||||
account = AccountStore.accounts()[0]
|
||||
account.organizationUnit = 'folder'
|
||||
# Converting to JSON removes keys whose values are `undefined`,
|
||||
# makes the output smaller and easier to visually compare.
|
||||
jsonAcc = JSON.parse(JSON.stringify(account))
|
||||
AccountSidebarStore._account = account
|
||||
|
||||
spyOn(CategoryStore, 'standardCategories').andReturn [
|
||||
new Folder(displayName:'Inbox', clientId: '1', name: 'inbox')
|
||||
new Folder(displayName:'Sent', clientId: '3', name: 'sent')
|
||||
new Folder(displayName:'Important', clientId: '4', name: 'important')
|
||||
]
|
||||
|
||||
spyOn(CategoryStore, 'userCategories').andReturn [
|
||||
new Folder(displayName:'A', clientId: 'a')
|
||||
new Folder(displayName:'B', clientId: 'b')
|
||||
new Folder(displayName:'A/B', clientId: 'a+b')
|
||||
new Folder(displayName:'A.D', clientId: 'a+d')
|
||||
new Folder(displayName:'A\\E', clientId: 'a+e')
|
||||
new Folder(displayName:'B/C', clientId: 'b+c')
|
||||
new Folder(displayName:'A/B/C', clientId: 'a+b+c')
|
||||
new Folder(displayName:'A/B-C', clientId: 'a+b-c')
|
||||
]
|
||||
|
||||
spyOn(WorkspaceStore, 'sidebarItems').andCallFake ->
|
||||
return [
|
||||
new WorkspaceStore.SidebarItem
|
||||
component: {}
|
||||
sheet: 'stub'
|
||||
id: 'Drafts'
|
||||
name: 'Drafts'
|
||||
]
|
||||
|
||||
# Note: If you replace this JSON with new JSON, you may have to replace
|
||||
# A\E with A\\E manually.
|
||||
expected = [{
|
||||
label: 'Mailboxes',
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Inbox',
|
||||
mailboxPerspective: {
|
||||
name: 'Inbox',
|
||||
category: {
|
||||
client_id: '1',
|
||||
name: 'inbox',
|
||||
display_name: 'Inbox',
|
||||
id: '1'
|
||||
},
|
||||
iconName: 'inbox.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: null
|
||||
},
|
||||
{
|
||||
id: 'starred',
|
||||
name: 'Starred',
|
||||
mailboxPerspective: {
|
||||
name: 'Starred',
|
||||
iconName: 'starred.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Sent',
|
||||
mailboxPerspective: {
|
||||
name: 'Sent',
|
||||
category: {
|
||||
client_id: '3',
|
||||
name: 'sent',
|
||||
display_name: 'Sent',
|
||||
id: '3'
|
||||
},
|
||||
iconName: 'sent.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Important',
|
||||
mailboxPerspective: {
|
||||
name: 'Important',
|
||||
category: {
|
||||
client_id: '4',
|
||||
name: 'important',
|
||||
display_name: 'Important',
|
||||
id: '4'
|
||||
},
|
||||
iconName: 'important.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
id: 'Drafts',
|
||||
component: {
|
||||
|
||||
},
|
||||
name: 'Drafts',
|
||||
sheet: 'stub',
|
||||
children: [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Folders',
|
||||
items: [
|
||||
{
|
||||
id: 'a',
|
||||
name: 'A',
|
||||
mailboxPerspective: {
|
||||
name: 'A',
|
||||
category: {
|
||||
client_id: 'a',
|
||||
display_name: 'A',
|
||||
id: 'a'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
{
|
||||
id: 'a+b',
|
||||
name: 'B',
|
||||
mailboxPerspective: {
|
||||
name: 'A/B',
|
||||
category: {
|
||||
client_id: 'a+b',
|
||||
display_name: 'A/B',
|
||||
id: 'a+b'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
{
|
||||
id: 'a+b+c',
|
||||
name: 'C',
|
||||
mailboxPerspective: {
|
||||
name: 'A/B/C',
|
||||
category: {
|
||||
client_id: 'a+b+c',
|
||||
display_name: 'A/B/C',
|
||||
id: 'a+b+c'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
}
|
||||
],
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
id: 'a+d',
|
||||
name: 'D',
|
||||
mailboxPerspective: {
|
||||
name: 'A.D',
|
||||
category: {
|
||||
client_id: 'a+d',
|
||||
display_name: 'A.D',
|
||||
id: 'a+d'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
id: 'a+e',
|
||||
name: 'E',
|
||||
mailboxPerspective: {
|
||||
name: 'A\\E',
|
||||
category: {
|
||||
client_id: 'a+e',
|
||||
display_name: 'A\\E',
|
||||
id: 'a+e'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
id: 'a+b-c',
|
||||
name: 'B-C',
|
||||
mailboxPerspective: {
|
||||
name: 'A/B-C',
|
||||
category: {
|
||||
client_id: 'a+b-c',
|
||||
display_name: 'A/B-C',
|
||||
id: 'a+b-c'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
}
|
||||
],
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
name: 'B',
|
||||
mailboxPerspective: {
|
||||
name: 'B',
|
||||
category: {
|
||||
client_id: 'b',
|
||||
display_name: 'B',
|
||||
id: 'b'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
{
|
||||
id: 'b+c',
|
||||
name: 'C',
|
||||
mailboxPerspective: {
|
||||
name: 'B/C',
|
||||
category: {
|
||||
client_id: 'b+c',
|
||||
display_name: 'B/C',
|
||||
id: 'b+c'
|
||||
},
|
||||
iconName: 'folder.png'
|
||||
account: jsonAcc
|
||||
},
|
||||
children: [
|
||||
|
||||
],
|
||||
unreadCount: 0
|
||||
}
|
||||
],
|
||||
unreadCount: 0
|
||||
}
|
||||
],
|
||||
iconName: 'folder.png'
|
||||
}]
|
||||
|
||||
AccountSidebarStore._updateSections()
|
||||
|
||||
# Converting to JSON removes keys whose values are `undefined`,
|
||||
# makes the output smaller and easier to visually compare.
|
||||
output = JSON.parse(JSON.stringify(AccountSidebarStore.sections()))
|
||||
|
||||
expect(output).toEqual(expected)
|
|
@ -1,7 +1,7 @@
|
|||
React = require 'react/addons'
|
||||
TestUtils = React.addons.TestUtils
|
||||
AccountSwitcher = require './../lib/account-switcher'
|
||||
AccountSidebarStore = require './../lib/account-sidebar-store'
|
||||
AccountSwitcher = require './../lib/components/account-switcher'
|
||||
SidebarStore = require './../lib/sidebar-store'
|
||||
{AccountStore} = require 'nylas-exports'
|
||||
|
||||
describe "AccountSwitcher", ->
|
||||
|
@ -17,7 +17,7 @@ describe "AccountSwitcher", ->
|
|||
label: "work"
|
||||
}
|
||||
]
|
||||
spyOn(AccountSidebarStore, "currentAccount").andReturn account
|
||||
spyOn(SidebarStore, "currentAccount").andReturn account
|
||||
|
||||
switcher = TestUtils.renderIntoDocument(
|
||||
<AccountSwitcher />
|
||||
|
|
|
@ -8,14 +8,17 @@ import RetinaImg from './retina-img';
|
|||
const CounterStyles = {
|
||||
Default: 'def',
|
||||
Alt: 'alt',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// TODO Docs
|
||||
class OutlineViewItem extends Component {
|
||||
static displayName = 'OutlineView'
|
||||
|
||||
static propTypes = {
|
||||
item: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
children: PropTypes.array,
|
||||
children: PropTypes.array.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
iconName: PropTypes.string.isRequired,
|
||||
count: PropTypes.number,
|
||||
|
@ -29,20 +32,7 @@ class OutlineViewItem extends Component {
|
|||
onDrop: PropTypes.func,
|
||||
onSelect: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
children: [],
|
||||
count: 0,
|
||||
counterStyle: CounterStyles.Default,
|
||||
dataTransferType: '',
|
||||
collapsed: false,
|
||||
deleted: false,
|
||||
selected: false,
|
||||
shouldAcceptDrop: ()=> false,
|
||||
onToggleCollapsed: ()=> {},
|
||||
onDrop: ()=> {},
|
||||
onSelect: ()=> {},
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
|
@ -50,7 +40,7 @@ class OutlineViewItem extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.onDelete != null) {
|
||||
if (this.props.item.onDelete) {
|
||||
React.findDOMNode(this).addEventListener('contextmenu', this._onShowContextMenu);
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +51,7 @@ class OutlineViewItem extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.props.onDelete != null) {
|
||||
if (this.props.item.onDelete) {
|
||||
React.findDOMNode(this).removeEventListener('contextmenu', this._onShowContextMenu);
|
||||
}
|
||||
}
|
||||
|
@ -69,30 +59,25 @@ class OutlineViewItem extends Component {
|
|||
static CounterStyles = CounterStyles;
|
||||
|
||||
|
||||
// Handlers
|
||||
// Helpers
|
||||
|
||||
_onShowContextMenu = ()=> {
|
||||
const item = this.props;
|
||||
const name = item.name;
|
||||
const {remote} = require('electron');
|
||||
const {Menu, MenuItem} = remote.require('electron');
|
||||
|
||||
const menu = new Menu();
|
||||
menu.append(new MenuItem({
|
||||
name: `Delete ${name}`,
|
||||
click: ()=> {
|
||||
item.onDelete(item.id);
|
||||
},
|
||||
}));
|
||||
menu.popup(remote.getCurrentWindow());
|
||||
_runCallback = (method, ...args)=> {
|
||||
const item = this.props.item;
|
||||
if (item[method]) {
|
||||
item[method](item, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handlers
|
||||
|
||||
_onDragStateChange = ({isDropping})=> {
|
||||
this.setState({isDropping});
|
||||
}
|
||||
|
||||
_onDrop = (event)=> {
|
||||
const jsonString = event.dataTransfer.getData(this.props.dataTransferType);
|
||||
const item = this.props.item;
|
||||
const jsonString = event.dataTransfer.getData(item.dataTransferType);
|
||||
let ids;
|
||||
try {
|
||||
ids = JSON.parse(jsonString);
|
||||
|
@ -101,18 +86,44 @@ class OutlineViewItem extends Component {
|
|||
}
|
||||
if (!ids) return;
|
||||
|
||||
this.props.onDrop(ids);
|
||||
this._runCallback('onDrop', ids);
|
||||
}
|
||||
|
||||
_onToggleCollapsed = ()=> {
|
||||
this._runCallback('onToggleCollapsed');
|
||||
}
|
||||
|
||||
_onClick = (event)=> {
|
||||
event.preventDefault();
|
||||
this.props.onSelect(this.props.id);
|
||||
this._runCallback('onSelect');
|
||||
}
|
||||
|
||||
_onDelete = ()=> {
|
||||
this._runCallback('onDelete');
|
||||
}
|
||||
|
||||
_shouldAcceptDrop = (event)=> {
|
||||
this._runCallback('shouldAcceptDrop', event);
|
||||
}
|
||||
|
||||
_onShowContextMenu = ()=> {
|
||||
const item = this.props.item;
|
||||
const name = item.name;
|
||||
const {remote} = require('electron');
|
||||
const {Menu, MenuItem} = remote.require('electron');
|
||||
|
||||
const menu = new Menu();
|
||||
menu.append(new MenuItem({
|
||||
label: `Delete ${name}`,
|
||||
click: this._onDelete,
|
||||
}));
|
||||
menu.popup(remote.getCurrentWindow());
|
||||
}
|
||||
|
||||
|
||||
// Renderers
|
||||
|
||||
_renderCount(item = this.props) {
|
||||
_renderCount(item = this.props.item) {
|
||||
if (!item.count) return <span></span>;
|
||||
const className = classnames({
|
||||
'item-count-box': true,
|
||||
|
@ -121,7 +132,7 @@ class OutlineViewItem extends Component {
|
|||
return <div className={className}>{item.count}</div>;
|
||||
}
|
||||
|
||||
_renderIcon(item = this.props) {
|
||||
_renderIcon(item = this.props.item) {
|
||||
return (
|
||||
<RetinaImg
|
||||
name={item.iconName}
|
||||
|
@ -130,7 +141,7 @@ class OutlineViewItem extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
_renderItem(item = this.props, state = this.state) {
|
||||
_renderItem(item = this.props.item, state = this.state) {
|
||||
const containerClass = classnames({
|
||||
'item': true,
|
||||
'selected': item.selected,
|
||||
|
@ -143,7 +154,7 @@ class OutlineViewItem extends Component {
|
|||
className={containerClass}
|
||||
onClick={this._onClick}
|
||||
id={item.id}
|
||||
shouldAcceptDrop={item.shouldAcceptDrop}
|
||||
shouldAcceptDrop={this._shouldAcceptDrop}
|
||||
onDragStateChange={this._onDragStateChange}
|
||||
onDrop={this._onDrop}>
|
||||
{this._renderCount()}
|
||||
|
@ -153,12 +164,12 @@ class OutlineViewItem extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
_renderChildren(item = this.props) {
|
||||
_renderChildren(item = this.props.item) {
|
||||
if (item.children.length > 0 && !item.collapsed) {
|
||||
return (
|
||||
<section key={`${item.id}-children`}>
|
||||
<section className="item-children" key={`${item.id}-children`}>
|
||||
{item.children.map(
|
||||
child => <OutlineViewItem key={child.id} {...child} />
|
||||
child => <OutlineViewItem key={child.id} item={child} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
@ -167,14 +178,15 @@ class OutlineViewItem extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const item = this.props;
|
||||
const item = this.props.item;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span className="item-container">
|
||||
<DisclosureTriangle
|
||||
collapsed={item.collapsed}
|
||||
visible={item.children.length > 0}
|
||||
onToggleCollapsed={item.onToggleCollapsed} />
|
||||
onToggleCollapsed={this._onToggleCollapsed} />
|
||||
{this._renderItem()}
|
||||
</span>
|
||||
{this._renderChildren()}
|
||||
|
|
|
@ -6,6 +6,7 @@ import RetinaImg from './retina-img';
|
|||
import OutlineViewItem from './outline-view-item';
|
||||
|
||||
|
||||
// TODO Docs
|
||||
class OutlineView extends Component {
|
||||
static displayName = 'OutlineView'
|
||||
|
||||
|
@ -26,6 +27,9 @@ class OutlineView extends Component {
|
|||
showCreateInput: false,
|
||||
}
|
||||
|
||||
|
||||
// Handlers
|
||||
|
||||
_onCreateButtonMouseDown = ()=> {
|
||||
this._clickingCreateButton = true;
|
||||
}
|
||||
|
@ -51,6 +55,9 @@ class OutlineView extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Renderers
|
||||
|
||||
_renderCreateButton() {
|
||||
const title = this.props.title;
|
||||
return (
|
||||
|
@ -84,7 +91,7 @@ class OutlineView extends Component {
|
|||
type="text"
|
||||
tabIndex="1"
|
||||
className="add-item-input"
|
||||
onKeyDown={_.partial(this._onInputKeyDown, _, section)}
|
||||
onKeyDown={this._onInputKeyDown}
|
||||
onBlur={this._onInputBlur}
|
||||
placeholder={placeholder}/>
|
||||
</div>
|
||||
|
@ -94,9 +101,7 @@ class OutlineView extends Component {
|
|||
|
||||
_renderItems() {
|
||||
return this.props.items.map(item => (
|
||||
<OutlineViewItem
|
||||
key={item.id}
|
||||
{...item} />
|
||||
<OutlineViewItem key={item.id} item={item} />
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ class Category extends Model
|
|||
@
|
||||
|
||||
displayType: ->
|
||||
AccountStore = require '../stores/account-store'
|
||||
if AccountStore.accountForId(@category.accountId).usesLabels()
|
||||
return 'label'
|
||||
else
|
||||
|
|
|
@ -38,7 +38,7 @@ class CategoryStore extends NylasStore
|
|||
all = all.concat(_.values(categories))
|
||||
all
|
||||
|
||||
# Public: Returns all of the standard categories for the current account.
|
||||
# Public: Returns all of the standard categories for the given account.
|
||||
#
|
||||
standardCategories: (accountOrId) ->
|
||||
@_standardCategories[asAccountId(accountOrId)] ? []
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
Reflux = require 'reflux'
|
||||
_ = require 'underscore'
|
||||
FocusedPerspectiveStore = require './focused-perspective-store'
|
||||
DatabaseStore = require './database-store'
|
||||
DraftStore = require './draft-store'
|
||||
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
|
||||
|
@ -17,38 +19,34 @@ The DraftCountStore is only available in the main window.
|
|||
|
||||
if not NylasEnv.isMainWindow() and not NylasEnv.inSpecMode() then return
|
||||
|
||||
DraftCountStore = Reflux.createStore
|
||||
init: ->
|
||||
@listenTo FocusedPerspectiveStore, @_onFocusedPerspectiveChanged
|
||||
@listenTo DraftStore, @_onDraftChanged
|
||||
@_view = FocusedPerspectiveStore.current()
|
||||
@_count = null
|
||||
_.defer => @_fetchCount()
|
||||
class DraftCountStore extends NylasStore
|
||||
|
||||
# Public: Returns the number of drafts in the user's mailbox
|
||||
count: ->
|
||||
@_count
|
||||
constructor: ->
|
||||
@_counts = {}
|
||||
@_total = 0
|
||||
@_disposable = Rx.Observable.fromQuery(
|
||||
DatabaseStore.findAll(Message).where([Message.attributes.draft.equal(true)])
|
||||
).subscribe(@_onDraftsChanged)
|
||||
|
||||
_onFocusedPerspectiveChanged: ->
|
||||
view = FocusedPerspectiveStore.current()
|
||||
if view? and not(view.isEqual(@_view))
|
||||
@_view = view
|
||||
@_onDraftChanged()
|
||||
totalCount: ->
|
||||
@_total
|
||||
|
||||
_onDraftChanged: ->
|
||||
@_fetchCountDebounced ?= _.debounce(@_fetchCount, 250)
|
||||
@_fetchCountDebounced()
|
||||
# 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]
|
||||
|
||||
_fetchCount: ->
|
||||
account = @_view?.account
|
||||
matchers = [
|
||||
Message.attributes.draft.equal(true)
|
||||
]
|
||||
matchers.push(Message.attributes.accountId.equal(account.accountId)) if account?
|
||||
|
||||
DatabaseStore.count(Message, matchers).then (count) =>
|
||||
return if @_count is count
|
||||
@_count = count
|
||||
_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 = DraftCountStore
|
||||
|
||||
module.exports = new DraftCountStore()
|
||||
|
|
|
@ -160,7 +160,7 @@ class CategoryMailboxPerspective extends MailboxPerspective
|
|||
if @_categories[0].name
|
||||
@iconName = "#{@_categories[0].name}.png"
|
||||
else
|
||||
@iconName = CategoryHelpers.categoryIconName(@accountIds[0])
|
||||
@iconName = CategoryHelpers.categoryIconName(AccountStore.accountForId(@accountIds[0]))
|
||||
|
||||
@
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
section {
|
||||
margin-bottom: @padding-base-vertical;
|
||||
}
|
||||
|
||||
section {
|
||||
section.item-children {
|
||||
padding-left: @padding-base-horizontal * 1.3;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: @text-color-very-subtle;
|
||||
|
|
Loading…
Reference in a new issue