diff --git a/exports/nylas-exports.coffee b/exports/nylas-exports.coffee
index 9facfa5d6..6a5e28907 100644
--- a/exports/nylas-exports.coffee
+++ b/exports/nylas-exports.coffee
@@ -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'
diff --git a/internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx b/internal_packages/account-sidebar/lib/account-sidebar-mail-view-item.cjsx
similarity index 52%
rename from internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx
rename to internal_packages/account-sidebar/lib/account-sidebar-mail-view-item.cjsx
index c44401b80..e29d9ac9a 100644
--- a/internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx
+++ b/internal_packages/account-sidebar/lib/account-sidebar-mail-view-item.cjsx
@@ -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 =
{@state.unreadCount}
containerClass = classNames
@@ -42,28 +46,22 @@ class AccountSidebarCategoryItem extends React.Component
@setState({isDropping}) }
onDrop={@_onDrop}>
{unread}
{@_renderIcon()}
- {@props.item.displayName}
+ {@props.mailView.name}
_renderIcon: ->
- if @props.sectionType is "category" and AccountStore.current()
- if AccountStore.current().usesLabels()
-
- else
-
- else if @props.sectionType is "mailboxes"
-
+
_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
diff --git a/internal_packages/account-sidebar/lib/account-sidebar-store.coffee b/internal_packages/account-sidebar/lib/account-sidebar-store.coffee
index f3be4324f..4b70d1725 100644
--- a/internal_packages/account-sidebar/lib/account-sidebar-store.coffee
+++ b/internal_packages/account-sidebar/lib/account-sidebar-store.coffee
@@ -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()
diff --git a/internal_packages/account-sidebar/lib/account-sidebar.cjsx b/internal_packages/account-sidebar/lib/account-sidebar.cjsx
index 2907152f7..5e86502a6 100644
--- a/internal_packages/account-sidebar/lib/account-sidebar.cjsx
+++ b/internal_packages/account-sidebar/lib/account-sidebar.cjsx
@@ -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
+
else
- itemClass = SidebarSheetItem
+ if item.sidebarComponent
+ itemClass = item.sidebarComponent
+ else
+ itemClass = SidebarSheetItem
-
+
_onStoreChange: =>
@setState @_getStateFromStores()
diff --git a/internal_packages/account-sidebar/stylesheets/account-sidebar.less b/internal_packages/account-sidebar/stylesheets/account-sidebar.less
index 3833a605f..93318cc7d 100644
--- a/internal_packages/account-sidebar/stylesheets/account-sidebar.less
+++ b/internal_packages/account-sidebar/stylesheets/account-sidebar.less
@@ -85,6 +85,7 @@
top:1px;
overflow: hidden;
padding-top: @padding-small-vertical;
+ padding-bottom:@padding-small-vertical;
line-height: @line-height-small;
}
&.selected {
diff --git a/internal_packages/calendar-bar/lib/calendar-bar-item.cjsx b/internal_packages/calendar-bar/lib/calendar-bar-item.cjsx
index 6dc4393ac..b0a2d79f9 100644
--- a/internal_packages/calendar-bar/lib/calendar-bar-item.cjsx
+++ b/internal_packages/calendar-bar/lib/calendar-bar-item.cjsx
@@ -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
diff --git a/internal_packages/category-picker/lib/category-picker.cjsx b/internal_packages/category-picker/lib/category-picker.cjsx
index 58dcf4db7..5778fef76 100644
--- a/internal_packages/category-picker/lib/category-picker.cjsx
+++ b/internal_packages/category-picker/lib/category-picker.cjsx
@@ -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
diff --git a/internal_packages/thread-list/lib/thread-list-icon.cjsx b/internal_packages/thread-list/lib/thread-list-icon.cjsx
index 953909f83..80ebef54d 100644
--- a/internal_packages/thread-list/lib/thread-list-icon.cjsx
+++ b/internal_packages/thread-list/lib/thread-list-icon.cjsx
@@ -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'
diff --git a/internal_packages/thread-list/lib/thread-list-store.coffee b/internal_packages/thread-list/lib/thread-list-store.coffee
index 2fd2e00a3..cc5283121 100644
--- a/internal_packages/thread-list/lib/thread-list-store.coffee
+++ b/internal_packages/thread-list/lib/thread-list-store.coffee
@@ -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: ->
diff --git a/internal_packages/thread-list/lib/thread-list.cjsx b/internal_packages/thread-list/lib/thread-list.cjsx
index 9d3ee621a..7739a403e 100644
--- a/internal_packages/thread-list/lib/thread-list.cjsx
+++ b/internal_packages/thread-list/lib/thread-list.cjsx
@@ -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 =
- 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()
@wideColumns = [c1, c2, c3, c4, c5]
diff --git a/internal_packages/thread-list/stylesheets/thread-list.less b/internal_packages/thread-list/stylesheets/thread-list.less
index cceef12e3..7d1fab204 100644
--- a/internal_packages/thread-list/stylesheets/thread-list.less
+++ b/internal_packages/thread-list/stylesheets/thread-list.less
@@ -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;
}
diff --git a/internal_packages/unread-notifications/lib/main.coffee b/internal_packages/unread-notifications/lib/main.coffee
index f59aaefd7..f806af37d 100644
--- a/internal_packages/unread-notifications/lib/main.coffee
+++ b/internal_packages/unread-notifications/lib/main.coffee
@@ -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: ->
diff --git a/spec-nylas/stores/focused-category-store-spec.coffee b/spec-nylas/stores/focused-category-store-spec.coffee
deleted file mode 100644
index 75e4ba31f..000000000
--- a/spec-nylas/stores/focused-category-store-spec.coffee
+++ /dev/null
@@ -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()
diff --git a/spec-nylas/stores/focused-mail-view-store-spec.coffee b/spec-nylas/stores/focused-mail-view-store-spec.coffee
new file mode 100644
index 000000000..5164c9343
--- /dev/null
+++ b/spec-nylas/stores/focused-mail-view-store-spec.coffee
@@ -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()
diff --git a/src/flux/actions.coffee b/src/flux/actions.coffee
index ca35c1b16..9e5332260 100644
--- a/src/flux/actions.coffee
+++ b/src/flux/actions.coffee
@@ -214,10 +214,10 @@ class Actions
*Scope: Window*
```
- Actions.focusCategory()
+ Actions.focusMailView()
```
###
- @focusCategory: ActionScopeWindow
+ @focusMailView: ActionScopeWindow
###
Public: If the message with the provided id is currently beign displayed in the
diff --git a/src/flux/stores/category-store.coffee b/src/flux/stores/category-store.coffee
index 66e643971..d340ee4fe 100644
--- a/src/flux/stores/category-store.coffee
+++ b/src/flux/stores/category-store.coffee
@@ -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)
diff --git a/src/flux/stores/focused-category-store.coffee b/src/flux/stores/focused-category-store.coffee
deleted file mode 100644
index 8efa81145..000000000
--- a/src/flux/stores/focused-category-store.coffee
+++ /dev/null
@@ -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()
diff --git a/src/flux/stores/focused-mail-view-store.coffee b/src/flux/stores/focused-mail-view-store.coffee
new file mode 100644
index 000000000..7d73e5ad1
--- /dev/null
+++ b/src/flux/stores/focused-mail-view-store.coffee
@@ -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()
diff --git a/src/flux/tasks/archive-thread-helper.coffee b/src/flux/tasks/archive-thread-helper.coffee
index b295a9d35..f19e42917 100644
--- a/src/flux/tasks/archive-thread-helper.coffee
+++ b/src/flux/tasks/archive-thread-helper.coffee
@@ -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}
diff --git a/src/mail-view-filter.coffee b/src/mail-view-filter.coffee
new file mode 100644
index 000000000..301f9332f
--- /dev/null
+++ b/src/mail-view-filter.coffee
@@ -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
diff --git a/src/sheet-toolbar.cjsx b/src/sheet-toolbar.cjsx
index 9f65cde0e..96317a6b0 100644
--- a/src/sheet-toolbar.cjsx
+++ b/src/sheet-toolbar.cjsx
@@ -34,7 +34,7 @@ class WindowTitle extends React.Component
{@state.title}
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