feat(starred): Replace FocusedContentStore with FocusedMailViewStore, allows for starred

Summary:
This diff fixes T3389 and makes it possible to define mail views which are not based on a category and focus them in the app.

I think that we need to create a new index on the starred attribute to make sure the query runs fast.

More tests WIP

Test Plan: Run tests, more coming soon!

Reviewers: dillon, evan

Reviewed By: evan

Maniphest Tasks: T3389

Differential Revision: https://phab.nylas.com/D1979
This commit is contained in:
Ben Gotow 2015-09-04 12:23:15 -07:00
parent ee41722891
commit 1339ef19d3
21 changed files with 360 additions and 230 deletions

View file

@ -62,6 +62,7 @@ class NylasExports
@require "Calendar", 'flux/models/calendar'
@require "Metadata", 'flux/models/metadata'
@require "DatabaseObjectRegistry", "database-object-registry"
@require "MailViewFilter", 'mail-view-filter'
# Exported so 3rd party packages can subclass Model
@load "Model", 'flux/models/model'
@ -110,7 +111,7 @@ class NylasExports
@require "FileDownloadStore", 'flux/stores/file-download-store'
@require "DraftStoreExtension", 'flux/stores/draft-store-extension'
@require "FocusedContentStore", 'flux/stores/focused-content-store'
@require "FocusedCategoryStore", 'flux/stores/focused-category-store'
@require "FocusedMailViewStore", 'flux/stores/focused-mail-view-store'
@require "FocusedContactsStore", 'flux/stores/focused-contacts-store'
@require "MessageBodyProcessor", 'flux/stores/message-body-processor'
@require "MessageStoreExtension", 'flux/stores/message-store-extension'

View file

@ -5,14 +5,18 @@ classNames = require 'classnames'
UnreadCountStore,
WorkspaceStore,
AccountStore,
FocusedCategoryStore,
FocusedMailViewStore,
ChangeLabelsTask,
ChangeFolderTask,
CategoryStore} = require 'nylas-exports'
{RetinaImg, DropZone} = require 'nylas-component-kit'
class AccountSidebarCategoryItem extends React.Component
@displayName: 'AccountSidebarCategoryItem'
class AccountSidebarMailViewItem extends React.Component
@displayName: 'AccountSidebarMailViewItem'
@propTypes:
select: React.PropTypes.bool
mailView: React.PropTypes.object.isRequired
constructor: (@props) ->
@state =
@ -32,7 +36,7 @@ class AccountSidebarCategoryItem extends React.Component
render: =>
unread = []
if @props.item.name is "inbox" and @state.unreadCount > 0
if @props.mailView.category?.name is "inbox" and @state.unreadCount > 0
unread = <div className="unread item-count-box">{@state.unreadCount}</div>
containerClass = classNames
@ -42,28 +46,22 @@ class AccountSidebarCategoryItem extends React.Component
<DropZone className={containerClass}
onClick={@_onClick}
id={@props.item.id}
id={@props.mailView.id}
shouldAcceptDrop={@_shouldAcceptDrop}
onDragStateChange={ ({isDropping}) => @setState({isDropping}) }
onDrop={@_onDrop}>
{unread}
<div className="icon">{@_renderIcon()}</div>
<div className="name">{@props.item.displayName}</div>
<div className="name">{@props.mailView.name}</div>
</DropZone>
_renderIcon: ->
if @props.sectionType is "category" and AccountStore.current()
if AccountStore.current().usesLabels()
<RetinaImg name={"tag.png"} mode={RetinaImg.Mode.ContentIsMask} />
else
<RetinaImg name={"folder.png"} mode={RetinaImg.Mode.ContentIsMask} />
else if @props.sectionType is "mailboxes"
<RetinaImg name={"#{@props.item.name}.png"} fallback={'folder.png'} mode={RetinaImg.Mode.ContentIsMask} />
<RetinaImg name={@props.mailView.iconName} fallback={'folder.png'} mode={RetinaImg.Mode.ContentIsMask} />
_shouldAcceptDrop: (e) =>
return false if @props.item.name in CategoryStore.LockedCategoryNames
return false if @props.item.name is FocusedCategoryStore.categoryName()
return false if @props.mailView.isEqual(FocusedMailViewStore.mailView())
return false unless @props.mailView.canApplyToThreads()
'nylas-thread-ids' in e.dataTransfer.types
_onDrop: (e) =>
@ -71,28 +69,14 @@ class AccountSidebarCategoryItem extends React.Component
try
ids = JSON.parse(jsonString)
catch err
console.error("AccountSidebarCategoryItem onDrop: JSON parse #{err}")
console.error("AccountSidebarMailViewItem onDrop: JSON parse #{err}")
return unless ids
if AccountStore.current().usesLabels()
currentLabel = FocusedCategoryStore.category()
if currentLabel and not (currentLabel in CategoryStore.LockedCategoryNames)
labelsToRemove = [currentLabel]
task = new ChangeLabelsTask
threads: ids,
labelsToAdd: [@props.item],
labelsToRemove: labelsToRemove
else
task = new ChangeFolderTask
folder: @props.item,
threads: ids
Actions.queueTask(task)
@props.mailView.applyToThreads(ids)
_onClick: (event) =>
event.preventDefault()
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
Actions.focusCategory(@props.item)
Actions.focusMailView(@props.mailView)
module.exports = AccountSidebarCategoryItem
module.exports = AccountSidebarMailViewItem

View file

@ -10,7 +10,8 @@ _ = require 'underscore'
Label,
Folder,
Message,
FocusedCategoryStore,
MailViewFilter,
FocusedMailViewStore,
NylasAPI,
Thread} = require 'nylas-exports'
@ -27,7 +28,7 @@ class AccountSidebarStore extends NylasStore
selected: ->
if WorkspaceStore.rootSheet() is WorkspaceStore.Sheet.Threads
FocusedCategoryStore.category()
FocusedMailViewStore.mailView()
else
WorkspaceStore.rootSheet()
@ -37,13 +38,17 @@ class AccountSidebarStore extends NylasStore
@listenTo CategoryStore, @_refreshSections
@listenTo WorkspaceStore, @_refreshSections
@listenTo DraftCountStore, @_refreshSections
@listenTo FocusedCategoryStore, => @trigger()
@listenTo FocusedMailViewStore, => @trigger()
_refreshSections: =>
account = AccountStore.current()
return unless account
viewFilterForCategory = (cat) ->
return MailViewFilter.forCategory(cat)
userCategories = CategoryStore.getUserCategories()
userCategoryViews = _.map(userCategories, viewFilterForCategory)
# Our drafts are displayed via the `DraftListSidebarItem` which
# is loading into the `Drafts` Sheet.
@ -51,7 +56,11 @@ class AccountSidebarStore extends NylasStore
standardCategories = _.reject standardCategories, (category) =>
category.name is "drafts"
standardCategories.push(WorkspaceStore.Sheet["Drafts"])
standardViews = _.map(standardCategories, viewFilterForCategory)
standardViews.push(WorkspaceStore.Sheet["Drafts"])
starredView = MailViewFilter.forStarred()
standardViews.splice(1, 0, starredView)
# Find root views, add the Views section
# featureSheets = _.filter WorkspaceStore.Sheet, (sheet) ->
@ -62,9 +71,15 @@ class AccountSidebarStore extends NylasStore
@_sections = []
# if featureSheets.length > 0
# @_sections.push { label: '', items: featureSheets, type: 'sheet' }
@_sections.push { label: 'Mailboxes', items: standardCategories, type: 'mailboxes' }
@_sections.push
label: 'Mailboxes'
items: standardViews
type: 'mailboxes'
# @_sections.push { label: 'Views', items: extraSheets, type: 'sheet' }
@_sections.push { label: CategoryStore.categoryLabel(), items: userCategories, type: 'category' }
@_sections.push
label: CategoryStore.categoryLabel()
items: userCategoryViews
type: 'category'
@trigger()

View file

@ -1,10 +1,10 @@
React = require 'react'
{Actions, Category} = require("nylas-exports")
{Actions, MailViewFilter} = require("nylas-exports")
{ScrollRegion} = require("nylas-component-kit")
SidebarDividerItem = require("./account-sidebar-divider-item")
SidebarSheetItem = require("./account-sidebar-sheet-item")
AccountSidebarStore = require ("./account-sidebar-store")
AccountSidebarCategoryItem = require("./account-sidebar-category-item")
AccountSidebarMailViewItem = require("./account-sidebar-mail-view-item")
class AccountSidebar extends React.Component
@displayName: 'AccountSidebar'
@ -44,18 +44,22 @@ class AccountSidebar extends React.Component
_itemComponents: (section) =>
section.items?.map (item) =>
return unless item
if item instanceof Category
itemClass = AccountSidebarCategoryItem
else if item.sidebarComponent
itemClass = item.sidebarComponent
if item instanceof MailViewFilter
<AccountSidebarMailViewItem
key={item.id ? item.type}
mailView={item}
select={ item.isEqual(@state.selected) }/>
else
itemClass = SidebarSheetItem
if item.sidebarComponent
itemClass = item.sidebarComponent
else
itemClass = SidebarSheetItem
<itemClass
key={item.id ? item.type}
item={item}
sectionType={section.type}
select={item.id is @state.selected?.id }/>
<itemClass
key={item.id ? item.type}
item={item}
sectionType={section.type}
select={item.id is @state.selected?.id }/>
_onStoreChange: =>
@setState @_getStateFromStores()

View file

@ -85,6 +85,7 @@
top:1px;
overflow: hidden;
padding-top: @padding-small-vertical;
padding-bottom:@padding-small-vertical;
line-height: @line-height-small;
}
&.selected {

View file

@ -2,6 +2,9 @@ React = require 'react'
{Actions} = require("nylas-exports")
moment = require 'moment'
# TODO: This file is out of date!
return
class CalendarBarItem extends React.Component
render: =>
style =
@ -28,7 +31,7 @@ class CalendarBarItem extends React.Component
_onClick: (event) =>
event.preventDefault()
Actions.focusCategory(@props.tag)
Actions.focusMailView(@props.tag)
module.exports = CalendarBarItem

View file

@ -10,7 +10,7 @@ React = require 'react'
WorkspaceStore,
ChangeLabelsTask,
ChangeFolderTask,
FocusedCategoryStore} = require 'nylas-exports'
FocusedMailViewStore} = require 'nylas-exports'
{Menu,
Popover,
@ -252,7 +252,7 @@ class CategoryPicker extends React.Component
_isUserFacing: (allInInbox, category) =>
hiddenCategories = []
currentCategoryId = FocusedCategoryStore.categoryId()
currentCategoryId = FocusedMailViewStore.mailView().categoryId()
if @_account?.usesLabels()
hiddenCategories = ["all", "spam", "trash", "drafts", "sent"]
if allInInbox

View file

@ -19,16 +19,16 @@ class ThreadListIcon extends React.Component
return 'thread-icon-star'
if @props.thread.unread
return 'thread-icon-unread'
return 'thread-icon-unread thread-icon-star-on-hover'
msgs = @_nonDraftMessages()
last = msgs[msgs.length - 1]
if msgs.length > 1 and last.from[0]?.isMe()
if Utils.isForwardedMessage(last)
return 'thread-icon-forwarded'
return 'thread-icon-forwarded thread-icon-star-on-hover'
else
return 'thread-icon-replied'
return 'thread-icon-replied thread-icon-star-on-hover'
return 'thread-icon-star-on-hover'

View file

@ -13,7 +13,7 @@ NylasStore = require 'nylas-store'
FocusedContentStore,
ArchiveThreadHelper,
TaskQueueStatusStore,
FocusedCategoryStore} = require 'nylas-exports'
FocusedMailViewStore} = require 'nylas-exports'
# Public: A mutable text container with undo/redo support and the ability
# to annotate logical regions in the text.
@ -37,7 +37,7 @@ class ThreadListStore extends NylasStore
@listenTo DatabaseStore, @_onDataChanged
@listenTo AccountStore, @_onAccountChanged
@listenTo FocusedCategoryStore, @_onCategoryChanged
@listenTo FocusedMailViewStore, @_onMailViewChanged
atom.config.observe 'core.workspace.mode', => @_autofocusForLayoutMode()
@ -45,7 +45,7 @@ class ThreadListStore extends NylasStore
# has hot yet been populated from the database with the list of
# categories and their corresponding ids. Once that is ready, the
# CategoryStore will trigger, which will update the
# FocusedCategoryStore, which will cause us to create a new
# FocusedMailViewStore, which will cause us to create a new
# @view.
_resetInstanceVars: ->
@ -67,23 +67,18 @@ class ThreadListStore extends NylasStore
@trigger(@)
createView: ->
categoryId = FocusedCategoryStore.categoryId()
mailViewFilter = FocusedMailViewStore.mailView()
account = AccountStore.current()
return unless account
if @_searchQuery
@setView(new SearchView(@_searchQuery, account.id))
else if account.id and categoryId
else if account.id and mailViewFilter
matchers = []
matchers.push Thread.attributes.accountId.equal(account.id)
matchers = matchers.concat(mailViewFilter.matchers())
if account.usesLabels()
matchers.push Thread.attributes.labels.contains(categoryId)
else if account.usesFolders()
matchers.push Thread.attributes.folders.contains(categoryId)
else
throw new Error("Invalid organizationUnit")
view = new DatabaseView Thread, {matchers}, (ids) =>
DatabaseStore.findAll(Message)
.where(Message.attributes.threadId.in(ids))
@ -101,7 +96,7 @@ class ThreadListStore extends NylasStore
# Inbound Events
_onCategoryChanged: ->
_onMailViewChanged: ->
@createView()
_onAccountChanged: ->

View file

@ -10,7 +10,7 @@ classNames = require 'classnames'
WorkspaceStore,
AccountStore,
CategoryStore,
FocusedCategoryStore} = require 'nylas-exports'
FocusedMailViewStore} = require 'nylas-exports'
ThreadListParticipants = require './thread-list-participants'
ThreadListQuickActions = require './thread-list-quick-actions'
@ -91,7 +91,7 @@ class ThreadList extends React.Component
if hasAttachments
attachment = <div className="thread-icon thread-icon-attachment"></div>
currentCategoryId = FocusedCategoryStore.categoryId()
currentCategoryId = FocusedMailViewStore.mailView()?.categoryId()
allCategoryId = CategoryStore.getStandardCategory('all')?.id
ignoredIds = [currentCategoryId, allCategoryId]
@ -116,7 +116,7 @@ class ThreadList extends React.Component
c5 = new ListTabular.Column
name: "HoverActions"
resolver: (thread) =>
currentCategoryId = FocusedCategoryStore.categoryId()
currentCategoryId = FocusedMailViewStore.mailView()?.categoryId()
<ThreadListQuickActions thread={thread} categoryId={currentCategoryId}/>
@wideColumns = [c1, c2, c3, c4, c5]

View file

@ -261,12 +261,11 @@
// stars
.thread-list .list-item:hover .thread-icon-star-on-hover:hover {
.thread-list .thread-icon-star:hover {
background-image:url(../static/images/thread-list/icon-star-action-hover-@2x.png);
background-size: 16px;
}
.thread-icon-star-on-hover:hover,
.thread-list .list-item:hover .thread-icon-star-on-hover {
.thread-list .thread-icon-star-on-hover:hover {
background-image:url(../static/images/thread-list/icon-star-hover-@2x.png);
background-size: 16px;
}

View file

@ -1,6 +1,7 @@
_ = require 'underscore'
{Thread,
Actions,
MailViewFilter,
AccountStore,
CategoryStore,
DatabaseStore} = require 'nylas-exports'
@ -44,7 +45,9 @@ module.exports =
atom.displayWindow()
if AccountStore.current().id isnt thread.accountId
Actions.selectAccountId(thread.accountId)
Actions.focusCategory(thread.categoryNamed('inbox'))
MailViewFilter filter = MailViewFilter.forCategory(thread.categoryNamed('inbox'))
Actions.focusMailView(filter)
Actions.setFocus(collection: 'thread', item: thread)
_notifyMessages: ->

View file

@ -1,87 +0,0 @@
_ = require 'underscore'
Label = require '../../src/flux/models/label'
Folder = require '../../src/flux/models/folder'
CategoryStore = require '../../src/flux/stores/category-store'
AccountStore = require '../../src/flux/stores/account-store'
FocusedCategoryStore = require '../../src/flux/stores/focused-category-store'
describe "FocusedCategoryStore", ->
beforeEach ->
spyOn(FocusedCategoryStore, 'trigger')
FocusedCategoryStore._category = null
afterEach ->
atom.testOrganizationUnit = null
testStore = ->
describe "_onCategoryStoreChanged", ->
it "should set the current category to Inbox when it is unset", ->
FocusedCategoryStore._category = null
FocusedCategoryStore._onCategoryStoreChanged()
expect(FocusedCategoryStore.category().id).toEqual(@inboxCategory.id)
it "should set the current category to Inbox when the current category no longer exists in the CategoryStore", ->
otherAccountInbox = @inboxCategory.clone()
otherAccountInbox.serverId = 'other-id'
FocusedCategoryStore._category = otherAccountInbox
FocusedCategoryStore._onCategoryStoreChanged()
expect(FocusedCategoryStore.category().id).toEqual(@inboxCategory.id)
describe "_onSearchQueryCommitted", ->
it "should clear the focused category and trigger when a search query is committed", ->
FocusedCategoryStore._onFocusCategory(@userCategory)
FocusedCategoryStore._onSearchQueryCommitted('bla')
expect(FocusedCategoryStore.trigger).toHaveBeenCalled()
expect(FocusedCategoryStore.category()).toBe(null)
expect(FocusedCategoryStore.categoryName()).toBe(null)
it "should restore the category that was previously focused and trigger when a search query is cleared", ->
FocusedCategoryStore._onFocusCategory(@userCategory)
FocusedCategoryStore._onSearchQueryCommitted('bla')
expect(FocusedCategoryStore.category()).toEqual(null)
expect(FocusedCategoryStore.categoryName()).toEqual(null)
FocusedCategoryStore._onSearchQueryCommitted('')
expect(FocusedCategoryStore.trigger).toHaveBeenCalled()
expect(FocusedCategoryStore.category().id).toEqual(@userCategory.id)
expect(FocusedCategoryStore.categoryName()).toEqual(null)
describe "_onFocusCategory", ->
it "should focus the category and trigger when Actions.focusCategory is called", ->
FocusedCategoryStore._onFocusCategory(@userCategory)
expect(FocusedCategoryStore.trigger).toHaveBeenCalled()
expect(FocusedCategoryStore.categoryName()).toBe(null)
expect(FocusedCategoryStore.category().id).toEqual(@userCategory.id)
it "should do nothing if the category is already focused", ->
FocusedCategoryStore._onFocusCategory(@inboxCategory)
spyOn(FocusedCategoryStore, '_setCategory')
FocusedCategoryStore._onFocusCategory(@inboxCategory)
expect(FocusedCategoryStore._setCategory).not.toHaveBeenCalled()
describe 'when using labels', ->
beforeEach ->
atom.testOrganizationUnit = 'label'
@inboxCategory = new Label(id: 'id-123', name: 'inbox', displayName: "INBOX")
@userCategory = new Label(id: 'id-456', name: null, displayName: "MyCategory")
spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
spyOn(CategoryStore, "byId").andCallFake (id) =>
return @inboxCategory if id is @inboxCategory.id
return @userCategory if id is @userCategory.id
return null
testStore()
describe 'when using folders', ->
beforeEach ->
atom.testOrganizationUnit = 'folder'
@inboxCategory = new Folder(id: 'id-123', name: 'inbox', displayName: "INBOX")
@userCategory = new Folder(id: 'id-456', name: null, displayName: "MyCategory")
spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
testStore()

View file

@ -0,0 +1,89 @@
_ = require 'underscore'
Label = require '../../src/flux/models/label'
Folder = require '../../src/flux/models/folder'
MailViewFilter = require '../../src/mail-view-filter'
CategoryStore = require '../../src/flux/stores/category-store'
AccountStore = require '../../src/flux/stores/account-store'
FocusedMailViewStore = require '../../src/flux/stores/focused-mail-view-store'
describe "FocusedMailViewStore", ->
beforeEach ->
spyOn(FocusedMailViewStore, 'trigger')
FocusedMailViewStore._mailView = null
afterEach ->
atom.testOrganizationUnit = null
testStore = ->
describe "_onCategoryStoreChanged", ->
it "should set the current category to Inbox when it is unset", ->
FocusedMailViewStore._mailView = null
FocusedMailViewStore._onCategoryStoreChanged()
expect(FocusedMailViewStore.mailView()).not.toBe(null)
expect(FocusedMailViewStore.mailView().categoryId()).toEqual(@inboxCategory.id)
it "should set the current category to Inbox when the current category no longer exists in the CategoryStore", ->
otherAccountInbox = @inboxCategory.clone()
otherAccountInbox.serverId = 'other-id'
FocusedMailViewStore._mailView = MailViewFilter.forCategory(otherAccountInbox)
FocusedMailViewStore._onCategoryStoreChanged()
expect(FocusedMailViewStore.mailView().categoryId()).toEqual(@inboxCategory.id)
describe "_onSearchQueryCommitted", ->
it "should clear the focused category and trigger when a search query is committed", ->
FocusedMailViewStore._onFocusMailView(@userFilter)
FocusedMailViewStore._onSearchQueryCommitted('bla')
expect(FocusedMailViewStore.trigger).toHaveBeenCalled()
expect(FocusedMailViewStore.mailView()).toBe(null)
it "should restore the category that was previously focused and trigger when a search query is cleared", ->
FocusedMailViewStore._onFocusMailView(@userFilter)
FocusedMailViewStore._onSearchQueryCommitted('bla')
expect(FocusedMailViewStore.mailView()).toEqual(null)
FocusedMailViewStore._onSearchQueryCommitted('')
expect(FocusedMailViewStore.trigger).toHaveBeenCalled()
expect(FocusedMailViewStore.mailView().categoryId()).toEqual(@userCategory.id)
describe "_onFocusMailView", ->
it "should focus the category and trigger when Actions.focusCategory is called", ->
FocusedMailViewStore._onFocusMailView(@userFilter)
expect(FocusedMailViewStore.trigger).toHaveBeenCalled()
expect(FocusedMailViewStore.mailView().categoryId()).toEqual(@userCategory.id)
it "should do nothing if the category is already focused", ->
FocusedMailViewStore._onFocusMailView(@inboxFilter)
spyOn(FocusedMailViewStore, '_setMailView')
FocusedMailViewStore._onFocusMailView(@inboxFilter)
expect(FocusedMailViewStore._setMailView).not.toHaveBeenCalled()
describe 'when using labels', ->
beforeEach ->
atom.testOrganizationUnit = 'label'
@inboxCategory = new Label(id: 'id-123', name: 'inbox', displayName: "INBOX")
@inboxFilter = MailViewFilter.forCategory(@inboxCategory)
@userCategory = new Label(id: 'id-456', name: null, displayName: "MyCategory")
@userFilter = MailViewFilter.forCategory(@userCategory)
spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
spyOn(CategoryStore, "byId").andCallFake (id) =>
return @inboxCategory if id is @inboxCategory.id
return @userCategory if id is @userCategory.id
return null
testStore()
describe 'when using folders', ->
beforeEach ->
atom.testOrganizationUnit = 'folder'
@inboxCategory = new Folder(id: 'id-123', name: 'inbox', displayName: "INBOX")
@inboxFilter = MailViewFilter.forCategory(@inboxCategory)
@userCategory = new Folder(id: 'id-456', name: null, displayName: "MyCategory")
@userFilter = MailViewFilter.forCategory(@userCategory)
spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
testStore()

View file

@ -214,10 +214,10 @@ class Actions
*Scope: Window*
```
Actions.focusCategory(<Category>)
Actions.focusMailView(<Category>)
```
###
@focusCategory: ActionScopeWindow
@focusMailView: ActionScopeWindow
###
Public: If the message with the provided id is currently beign displayed in the

View file

@ -36,6 +36,7 @@ class CategoryStore extends NylasStore
"drafts"
"all"
"archive"
"starred"
]
AllMailName: "all"
@ -97,7 +98,7 @@ class CategoryStore extends NylasStore
#
getUserCategories: ->
userCategories = _.reject _.values(@_categoryCache), (cat) =>
cat.name in @StandardCategoryNames
cat.name in @StandardCategoryNames or cat.name in @HiddenCategoryNames
userCategories = _.sortBy(userCategories, 'displayName')
return _.compact(userCategories)

View file

@ -1,56 +0,0 @@
NylasStore = require 'nylas-store'
CategoryStore = require './category-store'
AccountStore = require './account-store'
Actions = require '../actions'
class FocusedCategoryStore extends NylasStore
constructor: ->
@listenTo CategoryStore, @_onCategoryStoreChanged
@listenTo Actions.focusCategory, @_onFocusCategory
@listenTo Actions.searchQueryCommitted, @_onSearchQueryCommitted
@_onCategoryStoreChanged()
# Inbound Events
_onCategoryStoreChanged: ->
if @_category?.id
category = CategoryStore.byId(@_category.id)
category ?= @_defaultCategory()
@_setCategory(category)
_onFocusCategory: (category) ->
return if @_category?.id is category?.id
if @_category is null and category
Actions.searchQueryCommitted('')
@_setCategory(category)
_onSearchQueryCommitted: (query="") ->
if typeof(query) != "string"
query = query[0].all
if query.trim().length > 0 and @_category
@_categoryBeforeSearch = @_category
@_setCategory(null)
else if query.trim().length is 0
if @_categoryBeforeSearch
@_setCategory(@_categoryBeforeSearch)
else
@_setCategory(@_defaultCategory())
_defaultCategory: ->
CategoryStore.getStandardCategory('inbox')
_setCategory: (category) ->
return if @_category?.id is category?.id
@_category = category
@trigger()
# Public Methods
category: -> @_category ? null
categoryId: -> @_category?.id ? null
categoryName: -> @_category?.name ? null
module.exports = new FocusedCategoryStore()

View file

@ -0,0 +1,54 @@
NylasStore = require 'nylas-store'
MailViewFilter = require '../../mail-view-filter'
CategoryStore = require './category-store'
AccountStore = require './account-store'
Actions = require '../actions'
class FocusedMailViewStore extends NylasStore
constructor: ->
@listenTo CategoryStore, @_onCategoryStoreChanged
@listenTo Actions.focusMailView, @_onFocusMailView
@listenTo Actions.searchQueryCommitted, @_onSearchQueryCommitted
@_onCategoryStoreChanged()
# Inbound Events
_onCategoryStoreChanged: ->
if not @_mailView
@_setMailView(@_defaultMailView())
else
if not CategoryStore.byId(@_mailView.categoryId())
@_setMailView(@_defaultMailView())
_onFocusMailView: (filter) ->
return if filter.isEqual(@_mailView)
if @_mailView is null and filter
Actions.searchQueryCommitted('')
@_setMailView(filter)
_onSearchQueryCommitted: (query="") ->
if typeof(query) != "string"
query = query[0].all
if query.trim().length > 0 and @_mailView
@_mailViewBeforeSearch = @_mailView
@_setMailView(null)
else if query.trim().length is 0
if @_mailViewBeforeSearch
@_setMailView(@_mailViewBeforeSearch)
else
@_setMailView(@_defaultMailView())
_defaultMailView: ->
category = CategoryStore.getStandardCategory('inbox')
return null unless category
MailViewFilter.forCategory(category)
_setMailView: (filter) ->
return if filter?.isEqual(@_mailView)
@_mailView = filter
@trigger()
# Public Methods
mailView: -> @_mailView ? null
module.exports = new FocusedMailViewStore()

View file

@ -1,5 +1,5 @@
CategoryStore = require '../stores/category-store'
FocusedCategoryStore = require '../stores/focused-category-store'
FocusedMailViewStore = require '../stores/focused-mail-view-store'
ChangeLabelsTask = require './change-labels-task'
ChangeFolderTask = require './change-folder-task'
@ -48,7 +48,8 @@ class ArchiveThreadHelper
threads: threads
else if account.usesLabels()
currentLabel = FocusedCategoryStore.category()
viewCategoryId = FocusedMailViewStore.mailView().categoryId()
currentLabel = CategoryStore.byId(viewCategoryId)
currentLabel ?= CategoryStore.getStandardCategory("inbox")
params = {threads}

123
src/mail-view-filter.coffee Normal file
View file

@ -0,0 +1,123 @@
_ = require 'underscore'
AccountStore = require './flux/stores/account-store'
CategoryStore = require './flux/stores/category-store'
Thread = require './flux/models/thread'
Actions = require './flux/actions'
# This is a class cluster. Subclasses are not for external use!
# https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html
class MailViewFilter
# Factory Methods
@forCategory: (category) ->
new CategoryMailViewFilter(category)
@forStarred: ->
new StarredMailViewFilter()
# Instance Methods
constructor: ->
isEqual: (other) ->
return false unless other and @constructor.name is other.constructor.name
return false if other.name isnt @name
matchers = @matchers()
otherMatchers = other.matchers()
return false if otherMatchers.length isnt matchers.length
for idx in [0...matchers.length]
if matchers[idx].value() isnt otherMatchers[idx].value()
return false
true
categoryId: ->
throw new Error("categoryId: Not implemented in base class.")
matchers: ->
throw new Error("matchers: Not implemented in base class.")
canApplyToThreads: ->
throw new Error("canApplyToThreads: Not implemented in base class.")
applyToThreads: (threadsOrIds) ->
throw new Error("applyToThreads: Not implemented in base class.")
class StarredMailViewFilter extends MailViewFilter
constructor: ->
@name = "Starred"
@iconName = "starred.png"
@
matchers: ->
[Thread.attributes.starred.equal(true)]
categoryId: ->
null
canApplyToThreads: ->
true
applyToThreads: (threadsOrIds) ->
ChangeStarredTask = require './flux/tasks/change-starred-task'
task = new ChangeStarredTask({threads:threadsOrIds, starred: true})
Actions.queueTask(task)
class CategoryMailViewFilter extends MailViewFilter
constructor: (cat) ->
@name = cat.displayName
@category = cat
if cat.name
@iconName = "#{cat.name}.png"
else if AccountStore.current().usesLabels()
@iconName = "tag.png"
else
@iconName = "folder.png"
@
matchers: ->
account = AccountStore.current()
matchers = []
if account.usesLabels()
matchers.push Thread.attributes.labels.contains(@category.id)
else if account.usesFolders()
matchers.push Thread.attributes.folders.contains(@category.id)
matchers
categoryId: ->
@category.id
canApplyToThreads: ->
not (@category.name in CategoryStore.LockedCategoryNames)
applyToThreads: (threadsOrIds) ->
if AccountStore.current().usesLabels()
FocusedMailViewStore = require './flux/stores/focused-mail-view-store'
currentLabel = FocusedMailViewStore.mailView().category
if currentLabel and not (currentLabel in CategoryStore.LockedCategoryNames)
labelsToRemove = [currentLabel]
ChangeLabelsTask = require './flux/tasks/change-labels-task'
task = new ChangeLabelsTask
threads: threadsOrIds
labelsToAdd: [@category]
labelsToRemove: labelsToRemove
else
ChangeFolderTask = require './flux/tasks/change-folder-task'
task = new ChangeFolderTask
threads: threadsOrIds
folder: @category
Actions.queueTask(task)
module.exports = MailViewFilter

View file

@ -34,7 +34,7 @@ class WindowTitle extends React.Component
<div className="window-title">{@state.title}</div>
CategoryStore = null
FocusedCategoryStore = null
FocusedMailViewStore = null
class ToolbarBack extends React.Component
@displayName: 'ToolbarBack'
@ -42,13 +42,13 @@ class ToolbarBack extends React.Component
# This is because loading these stores has database side effects.
constructor: (@props) ->
CategoryStore ?= require './flux/stores/category-store'
FocusedCategoryStore ?= require './flux/stores/focused-category-store'
FocusedMailViewStore ?= require './flux/stores/focused-mail-view-store'
@state =
categoryName: FocusedCategoryStore.categoryName()
categoryName: FocusedMailViewStore.mailView().name
componentDidMount: =>
@_unsubscriber = FocusedCategoryStore.listen =>
@setState(categoryName: FocusedCategoryStore.categoryName())
@_unsubscriber = FocusedMailViewStore.listen =>
@setState(categoryName: FocusedMailViewStore.mailView().name)
componentWillUnmount: =>
@_unsubscriber() if @_unsubscriber