From 1339ef19d3c8124056bd8762c65afccc9487003b Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Fri, 4 Sep 2015 12:23:15 -0700 Subject: [PATCH] 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 --- exports/nylas-exports.coffee | 3 +- ...sx => account-sidebar-mail-view-item.cjsx} | 50 +++---- .../lib/account-sidebar-store.coffee | 27 +++- .../account-sidebar/lib/account-sidebar.cjsx | 28 ++-- .../stylesheets/account-sidebar.less | 1 + .../calendar-bar/lib/calendar-bar-item.cjsx | 5 +- .../category-picker/lib/category-picker.cjsx | 4 +- .../thread-list/lib/thread-list-icon.cjsx | 6 +- .../thread-list/lib/thread-list-store.coffee | 19 +-- .../thread-list/lib/thread-list.cjsx | 6 +- .../thread-list/stylesheets/thread-list.less | 5 +- .../unread-notifications/lib/main.coffee | 5 +- .../stores/focused-category-store-spec.coffee | 87 ------------- .../focused-mail-view-store-spec.coffee | 89 +++++++++++++ src/flux/actions.coffee | 4 +- src/flux/stores/category-store.coffee | 3 +- src/flux/stores/focused-category-store.coffee | 56 -------- .../stores/focused-mail-view-store.coffee | 54 ++++++++ src/flux/tasks/archive-thread-helper.coffee | 5 +- src/mail-view-filter.coffee | 123 ++++++++++++++++++ src/sheet-toolbar.cjsx | 10 +- 21 files changed, 360 insertions(+), 230 deletions(-) rename internal_packages/account-sidebar/lib/{account-sidebar-category-item.cjsx => account-sidebar-mail-view-item.cjsx} (52%) delete mode 100644 spec-nylas/stores/focused-category-store-spec.coffee create mode 100644 spec-nylas/stores/focused-mail-view-store-spec.coffee delete mode 100644 src/flux/stores/focused-category-store.coffee create mode 100644 src/flux/stores/focused-mail-view-store.coffee create mode 100644 src/mail-view-filter.coffee 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