mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-12-09 06:01:09 +08:00
Unified inbox mailbox perspectives working (sidebar disabled atm)
This commit is contained in:
parent
31f808ef35
commit
96f429ff39
34 changed files with 357 additions and 457 deletions
|
|
@ -19,7 +19,7 @@ _ = require 'underscore'
|
|||
class AccountSidebarStore extends NylasStore
|
||||
constructor: ->
|
||||
@_sections = []
|
||||
@_account = FocusedPerspectiveStore.current()?.account
|
||||
@_account = AccountStore.accounts()[0]
|
||||
@_registerListeners()
|
||||
@_updateSections()
|
||||
|
||||
|
|
@ -97,7 +97,9 @@ class AccountSidebarStore extends NylasStore
|
|||
category.name is "drafts"
|
||||
|
||||
standardCategoryItems = _.map standardCategories, (cat) => @_sidebarItemForCategory(cat)
|
||||
starredItem = @_sidebarItemForMailView('starred', MailboxPerspective.forStarred(@_account))
|
||||
|
||||
starredPerspective = MailboxPerspective.forStarred([@_account.id])
|
||||
starredItem = @_sidebarItemForMailView('starred', starredPerspective)
|
||||
|
||||
# Find root views and add them to the bottom of the list (Drafts, etc.)
|
||||
standardItems = standardCategoryItems
|
||||
|
|
@ -140,14 +142,13 @@ class AccountSidebarStore extends NylasStore
|
|||
new WorkspaceStore.SidebarItem
|
||||
id: category.id,
|
||||
name: shortenedName || category.displayName
|
||||
mailboxPerspective: MailboxPerspective.forCategory(@_account, category)
|
||||
mailboxPerspective: MailboxPerspective.forCategory(category)
|
||||
unreadCount: @_itemUnreadCount(category)
|
||||
|
||||
_createCategory: (displayName) ->
|
||||
# TODO this needs an account param
|
||||
return unless @_account?
|
||||
CategoryClass = @_account.categoryClass()
|
||||
category = new CategoryClass
|
||||
category = new Category
|
||||
displayName: displayName
|
||||
accountId: @_account.id
|
||||
Actions.queueTask(new SyncbackCategoryTask({category}))
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
React = require "react"
|
||||
AccountSwitcher = require "./account-switcher"
|
||||
AccountSidebar = require "./account-sidebar"
|
||||
# AccountSwitcher = require "./account-switcher"
|
||||
# AccountSidebar = require "./account-sidebar"
|
||||
{ComponentRegistry, WorkspaceStore} = require "nylas-exports"
|
||||
|
||||
module.exports =
|
||||
item: null # The DOM item the main React component renders into
|
||||
|
||||
activate: (@state) ->
|
||||
ComponentRegistry.register AccountSwitcher,
|
||||
location: WorkspaceStore.Location.RootSidebar
|
||||
|
||||
ComponentRegistry.register AccountSidebar,
|
||||
location: WorkspaceStore.Location.RootSidebar
|
||||
# ComponentRegistry.register AccountSwitcher,
|
||||
# location: WorkspaceStore.Location.RootSidebar
|
||||
#
|
||||
# ComponentRegistry.register AccountSidebar,
|
||||
# location: WorkspaceStore.Location.RootSidebar
|
||||
|
||||
deactivate: (@state) ->
|
||||
ComponentRegistry.unregister(AccountSwitcher)
|
||||
ComponentRegistry.unregister(AccountSidebar)
|
||||
# ComponentRegistry.unregister(AccountSwitcher)
|
||||
# ComponentRegistry.unregister(AccountSidebar)
|
||||
|
|
|
|||
|
|
@ -220,8 +220,7 @@ class CategoryPicker extends React.Component
|
|||
@refs.menu.setSelectedItem(null)
|
||||
|
||||
if item.newCategoryItem
|
||||
CategoryClass = @_account.categoryClass()
|
||||
category = new CategoryClass
|
||||
category = new Category
|
||||
displayName: @state.searchValue,
|
||||
accountId: @_account.id
|
||||
|
||||
|
|
@ -313,7 +312,8 @@ class CategoryPicker extends React.Component
|
|||
|
||||
_isUserFacing: (allInInbox, category) =>
|
||||
hiddenCategories = []
|
||||
currentCategoryId = FocusedPerspectiveStore.current()?.categoryId()
|
||||
currentCategories = FocusedPerspectiveStore.current().categories() ? []
|
||||
currentCategoryIds = _.pluck(currentCategories, 'id')
|
||||
|
||||
if @_account?.usesLabels()
|
||||
hiddenCategories = ["all", "drafts", "sent", "archive", "starred", "important"]
|
||||
|
|
@ -322,7 +322,7 @@ class CategoryPicker extends React.Component
|
|||
else if @_account?.usesFolders()
|
||||
hiddenCategories = ["drafts", "sent"]
|
||||
|
||||
return (category.name not in hiddenCategories) and (category.id isnt currentCategoryId)
|
||||
return (category.name not in hiddenCategories) and not (category.id in currentCategoryIds)
|
||||
|
||||
_allInInbox: (usageCount, numThreads) ->
|
||||
return unless @_account?
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ CategoryPicker = require '../lib/category-picker'
|
|||
{Popover} = require 'nylas-component-kit'
|
||||
|
||||
{Utils,
|
||||
Label,
|
||||
Folder,
|
||||
Category,
|
||||
Thread,
|
||||
Actions,
|
||||
AccountStore,
|
||||
|
|
@ -29,17 +28,15 @@ describe 'CategoryPicker', ->
|
|||
|
||||
setupFor = (organizationUnit) ->
|
||||
NylasEnv.testOrganizationUnit = organizationUnit
|
||||
@categoryClass = if organizationUnit is "label" then Label else Folder
|
||||
@account = {
|
||||
id: TEST_ACCOUNT_ID
|
||||
usesLabels: -> organizationUnit is "label"
|
||||
usesFolders: -> organizationUnit isnt "label"
|
||||
categoryClass: => @categoryClass
|
||||
}
|
||||
|
||||
@inboxCategory = new @categoryClass(id: 'id-123', name: 'inbox', displayName: "INBOX")
|
||||
@archiveCategory = new @categoryClass(id: 'id-456', name: 'archive', displayName: "ArCHIVe")
|
||||
@userCategory = new @categoryClass(id: 'id-789', name: null, displayName: "MyCategory")
|
||||
@inboxCategory = new Category(id: 'id-123', name: 'inbox', displayName: "INBOX")
|
||||
@archiveCategory = new Category(id: 'id-456', name: 'archive', displayName: "ArCHIVe")
|
||||
@userCategory = new Category(id: 'id-789', name: null, displayName: "MyCategory")
|
||||
|
||||
spyOn(Categories, "forAccount").andReturn NylasTestUtils.mockObservable(
|
||||
[@inboxCategory, @archiveCategory, @userCategory]
|
||||
|
|
@ -168,7 +165,7 @@ describe 'CategoryPicker', ->
|
|||
expect(Actions.queueTask).toHaveBeenCalled()
|
||||
syncbackTask = Actions.queueTask.calls[0].args[0]
|
||||
newCategory = syncbackTask.category
|
||||
expect(newCategory instanceof @categoryClass).toBe(true)
|
||||
expect(newCategory instanceof Category).toBe(true)
|
||||
expect(newCategory.displayName).toBe "teSTing!"
|
||||
expect(newCategory.accountId).toBe TEST_ACCOUNT_ID
|
||||
|
||||
|
|
|
|||
|
|
@ -38,19 +38,19 @@ class SearchSuggestionStore extends NylasStore
|
|||
|
||||
_onQuerySubmitted: (query) =>
|
||||
@_searchQuery = query
|
||||
perspective = FocusedPerspectiveStore.current()
|
||||
account = perspective.account
|
||||
current = FocusedPerspectiveStore.current()
|
||||
|
||||
if @_searchQuery.trim().length > 0
|
||||
@_perspectiveBeforeSearch ?= perspective
|
||||
Actions.focusMailboxPerspective(MailboxPerspective.forSearch(account, @_searchQuery))
|
||||
@_perspectiveBeforeSearch ?= current
|
||||
next = MailboxPerspective.forSearch(current.accountIds, @_searchQuery)
|
||||
Actions.focusMailboxPerspective(next)
|
||||
|
||||
else if @_searchQuery.trim().length is 0
|
||||
if @_perspectiveBeforeSearch
|
||||
Actions.focusMailboxPerspective(@_perspectiveBeforeSearch)
|
||||
@_perspectiveBeforeSearch = null
|
||||
else
|
||||
Actions.focusDefaultMailboxPerspectiveForAccount(account)
|
||||
Actions.focusDefaultMailboxPerspectiveForAccount(current.accountIds[0])
|
||||
|
||||
@_clearResults()
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ c3 = new ListTabular.Column
|
|||
if thread.hasAttachments
|
||||
attachment = <div className="thread-icon thread-icon-attachment"></div>
|
||||
|
||||
currentCategoryId = FocusedPerspectiveStore.current()?.categoryId()
|
||||
account = FocusedPerspectiveStore.current()?.account
|
||||
currentCategories = FocusedPerspectiveStore.current().categories() ? []
|
||||
account = FocusedPerspectiveStore.current().account
|
||||
|
||||
ignoredIds = [currentCategoryId]
|
||||
ignoredIds = _.pluck(currentCategories, 'id')
|
||||
ignoredIds.push(cat.id) for cat in CategoryStore.hiddenCategories(account)
|
||||
|
||||
for label in (thread.sortedLabels())
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ module.exports =
|
|||
else
|
||||
NylasEnv.displayWindow()
|
||||
|
||||
MailboxPerspective filter = MailboxPerspective.forCategory(account, thread.categoryNamed('inbox'))
|
||||
filter = MailboxPerspective.forCategory(thread.categoryNamed('inbox'))
|
||||
Actions.focusMailboxPerspective(filter)
|
||||
Actions.setFocus(collection: 'thread', item: thread)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,20 +24,20 @@ describe "FocusedPerspectiveStore", ->
|
|||
FocusedPerspectiveStore._perspective = null
|
||||
FocusedPerspectiveStore._onCategoryStoreChanged()
|
||||
expect(FocusedPerspectiveStore.current()).not.toBe(null)
|
||||
expect(FocusedPerspectiveStore.current().categoryId()).toEqual(@inboxCategory.id)
|
||||
expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory])
|
||||
|
||||
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'
|
||||
FocusedPerspectiveStore._perspective = MailboxPerspective.forCategory(@account, otherAccountInbox)
|
||||
FocusedPerspectiveStore._perspective = MailboxPerspective.forCategory(otherAccountInbox)
|
||||
FocusedPerspectiveStore._onCategoryStoreChanged()
|
||||
expect(FocusedPerspectiveStore.current().categoryId()).toEqual(@inboxCategory.id)
|
||||
expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory])
|
||||
|
||||
describe "_onFocusPerspective", ->
|
||||
it "should focus the category and trigger when Actions.focusCategory is called", ->
|
||||
FocusedPerspectiveStore._onFocusPerspective(@userFilter)
|
||||
expect(FocusedPerspectiveStore.trigger).toHaveBeenCalled()
|
||||
expect(FocusedPerspectiveStore.current().categoryId()).toEqual(@userCategory.id)
|
||||
expect(FocusedPerspectiveStore.current().categories()).toEqual([@userCategory])
|
||||
|
||||
it "should do nothing if the category is already focused", ->
|
||||
FocusedPerspectiveStore._onFocusPerspective(@inboxFilter)
|
||||
|
|
@ -50,9 +50,9 @@ describe "FocusedPerspectiveStore", ->
|
|||
NylasEnv.testOrganizationUnit = 'label'
|
||||
|
||||
@inboxCategory = new Label(id: 'id-123', name: 'inbox', displayName: "INBOX")
|
||||
@inboxFilter = MailboxPerspective.forCategory(@account, @inboxCategory)
|
||||
@inboxFilter = MailboxPerspective.forCategory(@inboxCategory)
|
||||
@userCategory = new Label(id: 'id-456', name: null, displayName: "MyCategory")
|
||||
@userFilter = MailboxPerspective.forCategory(@account, @userCategory)
|
||||
@userFilter = MailboxPerspective.forCategory(@userCategory)
|
||||
|
||||
spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
|
||||
spyOn(CategoryStore, "byId").andCallFake (id) =>
|
||||
|
|
@ -67,9 +67,9 @@ describe "FocusedPerspectiveStore", ->
|
|||
NylasEnv.testOrganizationUnit = 'folder'
|
||||
|
||||
@inboxCategory = new Folder(id: 'id-123', name: 'inbox', displayName: "INBOX")
|
||||
@inboxFilter = MailboxPerspective.forCategory(@account, @inboxCategory)
|
||||
@inboxFilter = MailboxPerspective.forCategory(@inboxCategory)
|
||||
@userCategory = new Folder(id: 'id-456', name: null, displayName: "MyCategory")
|
||||
@userFilter = MailboxPerspective.forCategory(@account, @userCategory)
|
||||
@userFilter = MailboxPerspective.forCategory(@userCategory)
|
||||
|
||||
spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
NylasAPI,
|
||||
Task,
|
||||
APIError,
|
||||
Label,
|
||||
Folder,
|
||||
Category,
|
||||
DatabaseStore,
|
||||
DatabaseTransaction} = require "nylas-exports"
|
||||
|
||||
|
|
@ -20,8 +19,8 @@ describe "DestroyCategoryTask", ->
|
|||
nameOf = (fn) ->
|
||||
fn.calls[0].args[0].body.display_name
|
||||
|
||||
makeTask = (CategoryClass) ->
|
||||
category = new CategoryClass
|
||||
makeTask = ->
|
||||
category = new Category
|
||||
displayName: "important emails"
|
||||
accountId: "account 123"
|
||||
serverId: "server-444"
|
||||
|
|
@ -34,7 +33,7 @@ describe "DestroyCategoryTask", ->
|
|||
|
||||
describe "performLocal", ->
|
||||
it "sets an `isDeleted` flag and persists the category", ->
|
||||
task = makeTask(Folder)
|
||||
task = makeTask()
|
||||
runs =>
|
||||
task.performLocal()
|
||||
waitsFor =>
|
||||
|
|
@ -47,7 +46,7 @@ describe "DestroyCategoryTask", ->
|
|||
describe "performRemote", ->
|
||||
it "throws error when no category present", ->
|
||||
waitsForPromise ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.category = null
|
||||
task.performRemote()
|
||||
.then ->
|
||||
|
|
@ -57,7 +56,7 @@ describe "DestroyCategoryTask", ->
|
|||
|
||||
it "throws error when category does not have a serverId", ->
|
||||
waitsForPromise ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.category.serverId = undefined
|
||||
task.performRemote()
|
||||
.then ->
|
||||
|
|
@ -70,22 +69,22 @@ describe "DestroyCategoryTask", ->
|
|||
spyOn(NylasAPI, "makeRequest").andCallFake -> Promise.resolve("null")
|
||||
|
||||
it "sends API req to /labels if user uses labels", ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.performRemote()
|
||||
expect(pathOf(NylasAPI.makeRequest)).toBe "/labels/server-444"
|
||||
|
||||
it "sends API req to /folders if user uses folders", ->
|
||||
task = makeTask(Folder)
|
||||
task = makeTask()
|
||||
task.performRemote()
|
||||
expect(pathOf(NylasAPI.makeRequest)).toBe "/folders/server-444"
|
||||
|
||||
it "sends DELETE request", ->
|
||||
task = makeTask(Folder)
|
||||
task = makeTask()
|
||||
task.performRemote()
|
||||
expect(methodOf(NylasAPI.makeRequest)).toBe "DELETE"
|
||||
|
||||
it "sends the account id", ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.performRemote()
|
||||
expect(accountIdOf(NylasAPI.makeRequest)).toBe "account 123"
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ describe "DestroyCategoryTask", ->
|
|||
|
||||
it "updates the isDeleted flag for the category and notifies error", ->
|
||||
waitsForPromise ->
|
||||
task = makeTask(Folder)
|
||||
task = makeTask()
|
||||
spyOn(task, "_notifyUserOfError")
|
||||
|
||||
task.performRemote().then (status) ->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
{Label,
|
||||
NylasAPI,
|
||||
Folder,
|
||||
{NylasAPI,
|
||||
DatabaseStore,
|
||||
SyncbackCategoryTask,
|
||||
DatabaseTransaction} = require "nylas-exports"
|
||||
|
|
@ -16,8 +14,8 @@ describe "SyncbackCategoryTask", ->
|
|||
nameOf = (fn) ->
|
||||
fn.calls[0].args[0].body.display_name
|
||||
|
||||
makeTask = (CategoryClass) ->
|
||||
category = new CategoryClass
|
||||
makeTask = ->
|
||||
category = new Category
|
||||
displayName: "important emails"
|
||||
accountId: "account 123"
|
||||
clientId: "local-444"
|
||||
|
|
@ -30,29 +28,29 @@ describe "SyncbackCategoryTask", ->
|
|||
spyOn(DatabaseTransaction.prototype, "_query").andCallFake => Promise.resolve([])
|
||||
spyOn(DatabaseTransaction.prototype, "persistModel")
|
||||
|
||||
it "sends API req to /labels if user uses labels", ->
|
||||
task = makeTask(Label)
|
||||
it "sends API req to /labels if the account uses labels", ->
|
||||
task = makeTask()
|
||||
task.performRemote({})
|
||||
expect(pathOf(NylasAPI.makeRequest)).toBe "/labels"
|
||||
|
||||
it "sends API req to /folders if user uses folders", ->
|
||||
task = makeTask(Folder)
|
||||
it "sends API req to /folders if the account uses folders", ->
|
||||
task = makeTask()
|
||||
task.performRemote({})
|
||||
expect(pathOf(NylasAPI.makeRequest)).toBe "/folders"
|
||||
|
||||
it "sends the account id", ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.performRemote({})
|
||||
expect(accountIdOf(NylasAPI.makeRequest)).toBe "account 123"
|
||||
|
||||
it "sends the display name in the body", ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.performRemote({})
|
||||
expect(nameOf(NylasAPI.makeRequest)).toBe "important emails"
|
||||
|
||||
it "adds server id to the category, then saves the category", ->
|
||||
waitsForPromise ->
|
||||
task = makeTask(Label)
|
||||
task = makeTask()
|
||||
task.performRemote({})
|
||||
.then ->
|
||||
expect(DatabaseTransaction.prototype.persistModel).toHaveBeenCalled()
|
||||
|
|
|
|||
|
|
@ -123,14 +123,14 @@ class ComponentRegistry
|
|||
if not descriptor?
|
||||
throw new Error("ComponentRegistry.findComponentsMatching called without descriptor")
|
||||
|
||||
cacheKey = JSON.stringify(descriptor)
|
||||
return @_cache[cacheKey] if @_cache[cacheKey]
|
||||
|
||||
{locations, modes, roles} = @_pluralizeDescriptor(descriptor)
|
||||
|
||||
if not locations and not modes and not roles
|
||||
throw new Error("ComponentRegistry.findComponentsMatching called with an empty descriptor")
|
||||
|
||||
cacheKey = JSON.stringify({locations, modes, roles})
|
||||
return [].concat(@_cache[cacheKey]) if @_cache[cacheKey]
|
||||
|
||||
# Made into a convenience function because default
|
||||
# values (`[]`) are necessary and it was getting messy.
|
||||
overlaps = (entry = [], search = []) ->
|
||||
|
|
@ -148,7 +148,8 @@ class ComponentRegistry
|
|||
|
||||
results = _.map entries, (entry) -> entry.component
|
||||
@_cache[cacheKey] = results
|
||||
return results
|
||||
|
||||
return [].concat(results)
|
||||
|
||||
triggerDebounced: _.debounce(( -> @trigger(@)), 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,43 +5,43 @@ _ = require 'underscore'
|
|||
module.exports =
|
||||
class MultiselectListInteractionHandler
|
||||
constructor: (@props) ->
|
||||
{@onFocusItemItem, @onSetCursorPosition} = @props
|
||||
{@onFocusItem, @onSetCursorPosition} = @props
|
||||
|
||||
cssClass: ->
|
||||
cssClass: =>
|
||||
'handler-list'
|
||||
|
||||
shouldShowFocus: ->
|
||||
shouldShowFocus: =>
|
||||
false
|
||||
|
||||
shouldShowCheckmarks: ->
|
||||
shouldShowCheckmarks: =>
|
||||
true
|
||||
|
||||
shouldShowKeyboardCursor: ->
|
||||
shouldShowKeyboardCursor: =>
|
||||
true
|
||||
|
||||
onClick: (item) ->
|
||||
onClick: (item) =>
|
||||
@onFocusItem(item)
|
||||
|
||||
onMetaClick: (item) ->
|
||||
onMetaClick: (item) =>
|
||||
@props.dataSource.selection.toggle(item)
|
||||
@onSetCursorPosition(item)
|
||||
|
||||
onShiftClick: (item) ->
|
||||
onShiftClick: (item) =>
|
||||
@props.dataSource.selection.expandTo(item)
|
||||
@onSetCursorPosition(item)
|
||||
|
||||
onEnter: ->
|
||||
onEnter: =>
|
||||
keyboardCursorId = @props.keyboardCursorId
|
||||
if keyboardCursorId
|
||||
item = @props.dataSource.getById(keyboardCursorId)
|
||||
@onFocusItem(item)
|
||||
|
||||
onSelect: ->
|
||||
onSelect: =>
|
||||
{id} = @_keyboardContext()
|
||||
return unless id
|
||||
@props.dataSource.selection.toggle(@props.dataSource.getById(id))
|
||||
|
||||
onShift: (delta, options = {}) ->
|
||||
onShift: (delta, options = {}) =>
|
||||
{id, action} = @_keyboardContext()
|
||||
|
||||
current = @props.dataSource.getById(id)
|
||||
|
|
@ -53,7 +53,7 @@ class MultiselectListInteractionHandler
|
|||
if options.select
|
||||
@props.dataSource.selection.walk({current, next})
|
||||
|
||||
_keyboardContext: ->
|
||||
_keyboardContext: =>
|
||||
if WorkspaceStore.topSheet().root
|
||||
{id: @props.keyboardCursorId, action: @onSetCursorPosition}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -7,39 +7,39 @@ class MultiselectSplitInteractionHandler
|
|||
constructor: (@props) ->
|
||||
{@onFocusItem, @onSetCursorPosition} = @props
|
||||
|
||||
cssClass: ->
|
||||
cssClass: =>
|
||||
'handler-split'
|
||||
|
||||
shouldShowFocus: ->
|
||||
shouldShowFocus: =>
|
||||
true
|
||||
|
||||
shouldShowCheckmarks: ->
|
||||
shouldShowCheckmarks: =>
|
||||
false
|
||||
|
||||
shouldShowKeyboardCursor: ->
|
||||
shouldShowKeyboardCursor: =>
|
||||
@props.dataSource.selection.count() > 1
|
||||
|
||||
onClick: (item) ->
|
||||
onClick: (item) =>
|
||||
@onFocusItem(item)
|
||||
@props.dataSource.selection.clear()
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onMetaClick: (item) ->
|
||||
onMetaClick: (item) =>
|
||||
@_turnFocusIntoSelection()
|
||||
@props.dataSource.selection.toggle(item)
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onShiftClick: (item) ->
|
||||
onShiftClick: (item) =>
|
||||
@_turnFocusIntoSelection()
|
||||
@props.dataSource.selection.expandTo(item)
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onEnter: ->
|
||||
onEnter: =>
|
||||
|
||||
onSelect: ->
|
||||
onSelect: =>
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onShift: (delta, options) ->
|
||||
onShift: (delta, options) =>
|
||||
if options.select
|
||||
@_turnFocusIntoSelection()
|
||||
|
||||
|
|
@ -63,12 +63,12 @@ class MultiselectSplitInteractionHandler
|
|||
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
_turnFocusIntoSelection: ->
|
||||
_turnFocusIntoSelection: =>
|
||||
focused = @props.focused
|
||||
@onFocusItem(null)
|
||||
@props.dataSource.selection.add(focused)
|
||||
|
||||
_checkSelectionAndFocusConsistency: ->
|
||||
_checkSelectionAndFocusConsistency: =>
|
||||
focused = @props.focused
|
||||
selection = @props.dataSource.selection
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class Matcher
|
|||
|
||||
joinSQL: (klass) ->
|
||||
switch @comparator
|
||||
when 'contains'
|
||||
when 'contains', 'containsAny'
|
||||
joinTable = tableNameForJoin(klass, @attr.itemClass)
|
||||
return "INNER JOIN `#{joinTable}` AS `M#{@muid}` ON `M#{@muid}`.`id` = `#{klass.name}`.`id`"
|
||||
else
|
||||
|
|
@ -93,7 +93,9 @@ class Matcher
|
|||
escaped = 0
|
||||
else if val instanceof Array
|
||||
escapedVals = []
|
||||
escapedVals.push("'#{v.replace(/'/g, '\\\'')}'") for v in val
|
||||
for v in val
|
||||
throw new Error("#{@attr.jsonKey} value #{v} must be a string.") unless _.isString(v)
|
||||
escapedVals.push("'#{v.replace(/'/g, '\\\'')}'")
|
||||
escaped = "(#{escapedVals.join(',')})"
|
||||
else
|
||||
escaped = val
|
||||
|
|
@ -106,7 +108,7 @@ class Matcher
|
|||
when 'containsAny'
|
||||
return "`M#{@muid}`.`value` IN #{escaped}"
|
||||
else
|
||||
return "`#{klass.tableName}`.`#{@attr.jsonKey}` #{@comparator} #{escaped}"
|
||||
return "`#{klass.name}`.`#{@attr.jsonKey}` #{@comparator} #{escaped}"
|
||||
|
||||
|
||||
Matcher.muid = 0
|
||||
|
|
|
|||
|
|
@ -89,14 +89,6 @@ class Account extends Model
|
|||
usesFolders: ->
|
||||
@organizationUnit is "folder"
|
||||
|
||||
categoryClass: ->
|
||||
if @usesLabels()
|
||||
return require './label'
|
||||
else if @usesFolders()
|
||||
return require './folder'
|
||||
else
|
||||
return null
|
||||
|
||||
# Public: Returns the localized, properly capitalized provider name,
|
||||
# like Gmail, Exchange, or Outlook 365
|
||||
displayProvider: ->
|
||||
|
|
|
|||
|
|
@ -82,6 +82,12 @@ class Category extends Model
|
|||
super
|
||||
@
|
||||
|
||||
displayType: ->
|
||||
if AccountStore.accountForId(@category.accountId).usesLabels()
|
||||
return 'label'
|
||||
else
|
||||
return 'folder'
|
||||
|
||||
hue: ->
|
||||
return 0 unless @displayName
|
||||
hue = 0
|
||||
|
|
|
|||
|
|
@ -2,49 +2,4 @@ _ = require 'underscore'
|
|||
Category = require './category'
|
||||
Attributes = require '../attributes'
|
||||
|
||||
###
|
||||
Public: The Folder model represents a Nylas Folder object. For more
|
||||
information about Folder on the Nylas Platform, read the [Folder API
|
||||
Documentation](https://nylas.com/docs/api#folders)
|
||||
|
||||
NOTE: This is different from a `Label`. A `Folder` is used for generic
|
||||
IMAP and Exchange, while `Label`s are used for Gmail. The `Account` has
|
||||
the filed `organizationUnit` which specifies if the current account uses
|
||||
either "folder" or "label".
|
||||
|
||||
While the two appear fairly similar, they have different behavioral
|
||||
semantics and are treated separately.
|
||||
|
||||
Nylas also exposes a set of standard types or categories of folders/
|
||||
labels: an extended version of [rfc-6154]
|
||||
(http://tools.ietf.org/html/rfc6154), returned as the name of the folder/
|
||||
label:
|
||||
- inbox
|
||||
- all
|
||||
- trash
|
||||
- archive
|
||||
- drafts
|
||||
- sent
|
||||
- spam
|
||||
- important
|
||||
|
||||
NOTE: "starred" and "unread" are no longer folder nor labels. They are now
|
||||
boolean values on messages and threads.
|
||||
|
||||
## Attributes
|
||||
|
||||
`name`: {AttributeString} The internal name of the folder. Queryable.
|
||||
|
||||
`displayName`: {AttributeString} The display-friendly name of the folder. Queryable.
|
||||
|
||||
Section: Models
|
||||
###
|
||||
class Folder extends Category
|
||||
|
||||
|
||||
@additionalSQLiteConfig:
|
||||
setup: ->
|
||||
['CREATE INDEX IF NOT EXISTS FolderNameIndex ON Folder(account_id,name)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS FolderClientIndex ON Folder(client_id)']
|
||||
|
||||
module.exports = Folder
|
||||
module.exports = Category
|
||||
|
|
|
|||
|
|
@ -2,48 +2,4 @@ _ = require 'underscore'
|
|||
Category = require './category'
|
||||
Attributes = require '../attributes'
|
||||
|
||||
###
|
||||
Public: The Label model represents a Nylas Label object. For more
|
||||
information about Label on the Nylas Platform, read the [Label API
|
||||
Documentation](https://nylas.com/docs/api#folders)
|
||||
|
||||
NOTE: This is different from a `Folder`. A `Folder` is used for generic
|
||||
IMAP and Exchange, while `Label`s are used for Gmail. The `Account` has
|
||||
the field `organizationUnit` which specifies if the current account uses
|
||||
either "folder" or "label".
|
||||
|
||||
While the two appear fairly similar, they have different behavioral
|
||||
semantics and are treated separately.
|
||||
|
||||
Nylas also exposes a set of standard types or categories of folders/
|
||||
labels: an extended version of [rfc-6154](http://tools.ietf.org/html/rfc6154),
|
||||
returned as the name of the folder/
|
||||
label:
|
||||
- inbox
|
||||
- all
|
||||
- trash
|
||||
- archive
|
||||
- drafts
|
||||
- sent
|
||||
- spam
|
||||
- important
|
||||
|
||||
NOTE: "starred" and "unread" are no longer folders nor labels. They are now
|
||||
boolean values on messages and threads.
|
||||
|
||||
## Attributes
|
||||
|
||||
`name`: {AttributeString} The internal name of the label. Queryable.
|
||||
|
||||
`displayName`: {AttributeString} The display-friendly name of the label. Queryable.
|
||||
|
||||
Section: Models
|
||||
###
|
||||
class Label extends Category
|
||||
|
||||
@additionalSQLiteConfig:
|
||||
setup: ->
|
||||
['CREATE INDEX IF NOT EXISTS LabelNameIndex ON Label(account_id,name)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS LabelClientIndex ON Label(client_id)']
|
||||
|
||||
module.exports = Label
|
||||
module.exports = Category
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ moment = require 'moment'
|
|||
|
||||
File = require './file'
|
||||
Utils = require './utils'
|
||||
Folder = require './folder'
|
||||
Model = require './model'
|
||||
Event = require './event'
|
||||
Category = require './category'
|
||||
Contact = require './contact'
|
||||
Attributes = require '../attributes'
|
||||
AccountStore = require '../stores/account-store'
|
||||
|
|
@ -146,7 +146,7 @@ class Message extends Model
|
|||
|
||||
'folder': Attributes.Object
|
||||
modelKey: 'folder'
|
||||
itemClass: Folder
|
||||
itemClass: Category
|
||||
|
||||
|
||||
@naturalSortOrder: ->
|
||||
|
|
|
|||
|
|
@ -156,8 +156,9 @@ class QuerySubscription
|
|||
console.warn("QuerySubscription: tried to publish a result set missing models.")
|
||||
return
|
||||
|
||||
unless _.uniq(@_set.ids()).length is @_set.count()
|
||||
throw new Error("")
|
||||
ids = @_set.ids()
|
||||
unless _.uniq(ids).length is ids.length
|
||||
throw new Error("QuerySubscription: result set contains duplicate ids.")
|
||||
|
||||
if @_options.asResultSet
|
||||
@_lastResult = @_set.immutableClone()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class ModelQuery
|
|||
@_database || = require '../stores/database-store'
|
||||
@_matchers = []
|
||||
@_orders = []
|
||||
@_distinct = false
|
||||
@_range = QueryRange.infinite()
|
||||
@_returnOne = false
|
||||
@_returnIds = false
|
||||
|
|
@ -57,11 +58,16 @@ class ModelQuery
|
|||
q._orders = [].concat(@_orders)
|
||||
q._includeJoinedData = [].concat(@_includeJoinedData)
|
||||
q._range = @_range.clone()
|
||||
q._distinct = @_distinct
|
||||
q._returnOne = @_returnOne
|
||||
q._returnIds = @_returnIds
|
||||
q._count = @_count
|
||||
q
|
||||
|
||||
distinct: ->
|
||||
@_distinct = true
|
||||
@
|
||||
|
||||
# Public: Add one or more where clauses to the query
|
||||
#
|
||||
# - `matchers` An {Array} of {Matcher} objects that add where clauses to the underlying query.
|
||||
|
|
@ -74,6 +80,8 @@ class ModelQuery
|
|||
if matchers instanceof Matcher
|
||||
@_matchers.push(matchers)
|
||||
else if matchers instanceof Array
|
||||
for m in matchers
|
||||
throw new Error("You must provide instances of `Matcher`") unless m instanceof Matcher
|
||||
@_matchers = @_matchers.concat(matchers)
|
||||
else if matchers instanceof Object
|
||||
# Support a shorthand format of {id: '123', accountId: '123'}
|
||||
|
|
@ -250,7 +258,9 @@ class ModelQuery
|
|||
limit = ""
|
||||
if @_range.offset?
|
||||
limit += " OFFSET #{@_range.offset}"
|
||||
"SELECT #{result} FROM `#{@_klass.name}` #{@_whereClause()} #{order} #{limit}"
|
||||
|
||||
distinct = if @_distinct then ' DISTINCT' else ''
|
||||
"SELECT#{distinct} #{result} FROM `#{@_klass.name}` #{@_whereClause()} #{order} #{limit}"
|
||||
|
||||
_whereClause: ->
|
||||
joins = []
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
_ = require 'underscore'
|
||||
|
||||
Label = require './label'
|
||||
Folder = require './folder'
|
||||
Category = require './category'
|
||||
Model = require './model'
|
||||
Contact = require './contact'
|
||||
Actions = require '../actions'
|
||||
|
|
@ -61,15 +60,10 @@ class Thread extends Model
|
|||
queryable: true
|
||||
modelKey: 'version'
|
||||
|
||||
'folders': Attributes.Collection
|
||||
'categories': Attributes.Collection
|
||||
queryable: true
|
||||
modelKey: 'folders'
|
||||
itemClass: Folder
|
||||
|
||||
'labels': Attributes.Collection
|
||||
queryable: true
|
||||
modelKey: 'labels'
|
||||
itemClass: Label
|
||||
modelKey: 'categories'
|
||||
itemClass: Category
|
||||
|
||||
'participants': Attributes.Collection
|
||||
modelKey: 'participants'
|
||||
|
|
@ -83,6 +77,24 @@ class Thread extends Model
|
|||
modelKey: 'lastMessageReceivedTimestamp'
|
||||
jsonKey: 'last_message_received_timestamp'
|
||||
|
||||
Object.defineProperty @prototype, "labels",
|
||||
enumerable: false
|
||||
get: -> @categories
|
||||
set: (v) -> @categories = v
|
||||
|
||||
Object.defineProperty @prototype, "folders",
|
||||
enumerable: false
|
||||
get: -> @categories
|
||||
set: (v) -> @categories = v
|
||||
|
||||
Object.defineProperty @attributes, "labels",
|
||||
enumerable: false
|
||||
get: -> @categories
|
||||
|
||||
Object.defineProperty @attributes, "folders",
|
||||
enumerable: false
|
||||
get: -> @categories
|
||||
|
||||
@naturalSortOrder: ->
|
||||
Thread.attributes.lastMessageReceivedTimestamp.descending()
|
||||
|
||||
|
|
@ -91,8 +103,19 @@ class Thread extends Model
|
|||
['CREATE INDEX IF NOT EXISTS ThreadListIndex ON Thread(account_id, last_message_received_timestamp DESC, id)']
|
||||
|
||||
fromJSON: (json) ->
|
||||
if json['labels']
|
||||
@_jsonCategoryType = 'labels'
|
||||
else
|
||||
@_jsonCategoryType = 'folders'
|
||||
json['categories'] = json[@_jsonCategoryType]
|
||||
super(json)
|
||||
|
||||
toJSON: (options) ->
|
||||
json = super(options)
|
||||
json[@_jsonCategoryType] = json['categories']
|
||||
delete json['categories']
|
||||
json
|
||||
|
||||
# Public: Returns true if the thread has a {Category} with the given
|
||||
# name. Note, only catgories of type `Category.Types.Standard` have valid
|
||||
# `names`
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
_ = require 'underscore'
|
||||
NylasStore = require 'nylas-store'
|
||||
AccountStore = require './account-store'
|
||||
Account = require '../models/account'
|
||||
{StandardCategoryNames} = require '../models/category'
|
||||
{Categories} = require 'nylas-observables'
|
||||
Rx = require 'rx-lite'
|
||||
|
||||
asAccount = (a) -> if a instanceof Account then a else AccountStore.accountForId(a)
|
||||
asAccountId = (a) -> if a instanceof Account then a.id else a
|
||||
|
||||
class CategoryStore extends NylasStore
|
||||
|
||||
constructor: ->
|
||||
|
|
@ -12,19 +16,22 @@ class CategoryStore extends NylasStore
|
|||
@_standardCategories = {}
|
||||
@_userCategories = {}
|
||||
@_hiddenCategories = {}
|
||||
@_registerObservables(AccountStore.accounts())
|
||||
@listenTo AccountStore, @_onAccountsChanged
|
||||
|
||||
byId: (account, categoryId) ->
|
||||
@categories(account)[categoryId]
|
||||
@_disposable = Categories
|
||||
.forAllAccounts()
|
||||
.sort()
|
||||
.subscribe(@_onCategoriesChanged)
|
||||
|
||||
byId: (accountOrId, categoryId) ->
|
||||
@categories(accountOrId)[categoryId]
|
||||
|
||||
# Public: Returns an array of all categories for an account, both
|
||||
# standard and user generated. The items returned by this function will be
|
||||
# either {Folder} or {Label} objects.
|
||||
#
|
||||
categories: (account) ->
|
||||
if account
|
||||
@_categoryCache[account.id] ? {}
|
||||
categories: (accountOrId = null) ->
|
||||
if accountOrId
|
||||
@_categoryCache[asAccountId(accountOrId)] ? {}
|
||||
else
|
||||
all = []
|
||||
for accountId, categories of @_categoryCache
|
||||
|
|
@ -33,89 +40,76 @@ class CategoryStore extends NylasStore
|
|||
|
||||
# Public: Returns all of the standard categories for the current account.
|
||||
#
|
||||
standardCategories: (account) ->
|
||||
return [] unless account
|
||||
_.compact(
|
||||
StandardCategoryNames.map (name) => @_standardCategories[account.id][name]
|
||||
)
|
||||
standardCategories: (accountOrId) ->
|
||||
return [] unless accountOrId
|
||||
@_standardCategories[asAccountId(accountOrId)]
|
||||
|
||||
hiddenCategories: (account) ->
|
||||
return [] unless account
|
||||
@_hiddenCategories[account.id]
|
||||
hiddenCategories: (accountOrId) ->
|
||||
return [] unless accountOrId
|
||||
@_hiddenCategories[asAccountId(accountOrId)]
|
||||
|
||||
# Public: Returns all of the categories that are not part of the standard
|
||||
# category set.
|
||||
#
|
||||
userCategories: (account) ->
|
||||
return [] unless account
|
||||
@_userCategories[account.id]
|
||||
userCategories: (accountOrId) ->
|
||||
return [] unless accountOrId
|
||||
@_userCategories[asAccountId(accountOrId)]
|
||||
|
||||
# Public: Returns the Folder or Label object for a standard category name and
|
||||
# for a given account.
|
||||
# ('inbox', 'drafts', etc.) It's possible for this to return `null`.
|
||||
# For example, Gmail likely doesn't have an `archive` label.
|
||||
#
|
||||
getStandardCategory: (account, name) ->
|
||||
return null unless account?
|
||||
if not name in StandardCategoryNames
|
||||
getStandardCategory: (accountOrId, name) ->
|
||||
return null unless accountOrId
|
||||
|
||||
unless name in StandardCategoryNames
|
||||
throw new Error("'#{name}' is not a standard category")
|
||||
return _.findWhere(@categories(account), {name})
|
||||
|
||||
return _.findWhere(@_standardCategories[asAccountId(accountOrId)], {name})
|
||||
|
||||
# Public: Returns the Folder or Label object that should be used for "Archive"
|
||||
# actions. On Gmail, this is the "all" label. On providers using folders, it
|
||||
# returns any available "Archive" folder, or null if no such folder exists.
|
||||
#
|
||||
getArchiveCategory: (account) ->
|
||||
return null unless account
|
||||
getArchiveCategory: (accountOrId) ->
|
||||
return null unless accountOrId
|
||||
account = asAccount(accountOrId)
|
||||
|
||||
if account.usesFolders()
|
||||
return @getStandardCategory(account, "archive")
|
||||
return @getStandardCategory(account.id, "archive")
|
||||
else
|
||||
return @getStandardCategory(account, "all")
|
||||
return @getStandardCategory(account.id, "all")
|
||||
|
||||
# Public: Returns the Folder or Label object taht should be used for
|
||||
# "Move to Trash", or null if no trash folder exists.
|
||||
#
|
||||
getTrashCategory: (account) ->
|
||||
@getStandardCategory(account, "trash")
|
||||
getTrashCategory: (accountOrId) ->
|
||||
@getStandardCategory(accountOrId, "trash")
|
||||
|
||||
_onAccountsChanged: ->
|
||||
accounts = AccountStore.accounts()
|
||||
@_removeStaleCategories(accounts)
|
||||
@_registerObservables(accounts)
|
||||
_onCategoriesChanged: (categories) =>
|
||||
@_categoryCache = {}
|
||||
for cat in categories
|
||||
@_categoryCache[cat.accountId] ?= {}
|
||||
@_categoryCache[cat.accountId][cat.id] = cat
|
||||
|
||||
_onCategoriesChanged: (accountId, categories) =>
|
||||
return unless categories
|
||||
@_categoryCache[accountId] = {}
|
||||
@_standardCategories[accountId] = {}
|
||||
@_userCategories[accountId] = []
|
||||
@_hiddenCategories[accountId] = []
|
||||
filteredByAccount = (fn) ->
|
||||
result = {}
|
||||
for cat in categories
|
||||
continue unless fn(cat)
|
||||
result[cat.accountId] ?= []
|
||||
result[cat.accountId].push(cat)
|
||||
result
|
||||
|
||||
@_standardCategories = filteredByAccount (cat) -> cat.isStandardCategory()
|
||||
@_userCategories = filteredByAccount (cat) -> cat.isUserCategory()
|
||||
@_hiddenCategories = filteredByAccount (cat) -> cat.isHiddenCategory()
|
||||
|
||||
# Ensure standard categories are always sorted in the correct order
|
||||
for accountId, items of @_standardCategories
|
||||
@_standardCategories[accountId].sort (a, b) ->
|
||||
StandardCategoryNames.indexOf(a.name) - StandardCategoryNames.indexOf(b.name)
|
||||
|
||||
for category in categories
|
||||
@_categoryCache[accountId][category.id] = category
|
||||
if category.isStandardCategory()
|
||||
@_standardCategories[accountId][category.name] = category
|
||||
if category.isUserCategory()
|
||||
@_userCategories[accountId].push(category)
|
||||
if category.isHiddenCategory()
|
||||
@_hiddenCategories[accountId].push(category)
|
||||
@trigger()
|
||||
|
||||
# Remove any category sets for removed accounts
|
||||
# Will prevent memory leaks
|
||||
_removeStaleCategories: (accounts) ->
|
||||
accountIds = accounts.map (acc) -> acc.id
|
||||
removedAccountIds = _.difference(_.keys(@_categoryCache), accountIds)
|
||||
for accountId in removedAccountIds
|
||||
delete @_categoryCache[accountId]
|
||||
delete @_standardCategories[accountId]
|
||||
delete @_userCategories[accountId]
|
||||
delete @_hiddenCategories[accountId]
|
||||
|
||||
_registerObservables: (accounts) =>
|
||||
@_disposables ?= []
|
||||
@_disposables.forEach (disp) -> disp.dispose()
|
||||
@_disposables = accounts.map (account) =>
|
||||
Categories.forAccount(account).sort()
|
||||
.subscribe(@_onCategoriesChanged.bind(@, account.id))
|
||||
|
||||
module.exports = new CategoryStore()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ DatabaseTransaction = require './database-transaction'
|
|||
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
DatabaseVersion = 16
|
||||
DatabaseVersion = 17
|
||||
DatabasePhase =
|
||||
Setup: 'setup'
|
||||
Ready: 'ready'
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ class FocusedPerspectiveStore extends NylasStore
|
|||
@_setPerspective(@_defaultPerspective())
|
||||
else
|
||||
account = @_current.account
|
||||
catId = @_current.categoryId()
|
||||
if catId and not CategoryStore.byId(account, catId)
|
||||
cats = @_current.categories()
|
||||
catExists = (cat) -> CategoryStore.byId(cat.accountId, cat.id)
|
||||
|
||||
if cats and not _.every(cats, catExists)
|
||||
@_setPerspective(@_defaultPerspective(account))
|
||||
|
||||
_onFocusPerspective: (perspective) =>
|
||||
|
|
@ -47,14 +49,13 @@ class FocusedPerspectiveStore extends NylasStore
|
|||
return unless account
|
||||
category = CategoryStore.getStandardCategory(account, "inbox")
|
||||
return unless category
|
||||
@_setPerspective(MailboxPerspective.forCategory(account, category))
|
||||
@_setPerspective(MailboxPerspective.forCategory(category))
|
||||
|
||||
# TODO Update unified MailboxPerspective
|
||||
_defaultPerspective: (account = AccountStore.accounts()[0])->
|
||||
_defaultPerspective: (account = AccountStore.accounts()[0]) ->
|
||||
return MailboxPerspective.forNothing() unless account
|
||||
category = CategoryStore.getStandardCategory(account, "inbox")
|
||||
return null unless category
|
||||
return MailboxPerspective.forCategory(account, category)
|
||||
# MailboxPerspective.unified()
|
||||
return MailboxPerspective.forNothing() unless category
|
||||
return MailboxPerspective.forCategory(category)
|
||||
|
||||
_setPerspective: (perspective) ->
|
||||
return if perspective?.isEqual(@_current)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ AccountStore = require './account-store'
|
|||
DatabaseStore = require './database-store'
|
||||
Actions = require '../actions'
|
||||
Thread = require '../models/thread'
|
||||
Folder = require '../models/folder'
|
||||
Label = require '../models/label'
|
||||
Category = require '../models/category'
|
||||
WindowBridge = require '../../window-bridge'
|
||||
|
||||
JSONBlobKey = 'UnreadCounts-V2'
|
||||
|
|
@ -19,15 +18,12 @@ class CategoryDatabaseMutationObserver
|
|||
beforeDatabaseChange: (query, {type, objects, objectIds, objectClass}) =>
|
||||
if objectClass is Thread.name
|
||||
idString = "'" + objectIds.join("','") + "'"
|
||||
Promise.props
|
||||
labelData: query("SELECT `Thread`.id as id, `Thread-Label`.`value` as catId FROM `Thread` INNER JOIN `Thread-Label` ON `Thread`.`id` = `Thread-Label`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", [])
|
||||
folderData: query("SELECT `Thread`.id as id, `Thread-Folder`.`value` as catId FROM `Thread` INNER JOIN `Thread-Folder` ON `Thread`.`id` = `Thread-Folder`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", [])
|
||||
.then ({labelData, folderData}) =>
|
||||
query("SELECT `Thread`.id as id, `Thread-Category`.`value` as catId FROM `Thread` INNER JOIN `Thread-Category` ON `Thread`.`id` = `Thread-Category`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", [])
|
||||
.then (categoryData) =>
|
||||
categories = {}
|
||||
for collection in [labelData, folderData]
|
||||
for {id, catId} in collection
|
||||
categories[catId] ?= 0
|
||||
categories[catId] -= 1
|
||||
for {id, catId} in categoryData
|
||||
categories[catId] ?= 0
|
||||
categories[catId] -= 1
|
||||
Promise.resolve({categories})
|
||||
else
|
||||
Promise.resolve()
|
||||
|
|
@ -68,11 +64,8 @@ class ThreadCountsStore extends NylasStore
|
|||
|
||||
if NylasEnv.isWorkWindow()
|
||||
DatabaseStore.findJSONBlob(JSONBlobKey).then(@_onCountsBlobRead)
|
||||
Rx.Observable.combineLatest(
|
||||
Rx.Observable.fromQuery(DatabaseStore.findAll(Label)),
|
||||
Rx.Observable.fromQuery(DatabaseStore.findAll(Folder))
|
||||
).subscribe ([labels, folders]) =>
|
||||
@_categories = [].concat(labels, folders)
|
||||
Rx.Observable.fromQuery(DatabaseStore.findAll(Category)).subscribe (categories) =>
|
||||
@_categories = [].concat(categories)
|
||||
@_fetchCountsMissing()
|
||||
|
||||
else
|
||||
|
|
@ -137,17 +130,10 @@ class ThreadCountsStore extends NylasStore
|
|||
@trigger()
|
||||
|
||||
_fetchCountForCategory: (cat) =>
|
||||
if cat instanceof Label
|
||||
categoryAttribute = Thread.attributes.labels
|
||||
else if cat instanceof Folder
|
||||
categoryAttribute = Thread.attributes.folders
|
||||
else
|
||||
throw new Error("Unexpected category class")
|
||||
|
||||
DatabaseStore.count(Thread, [
|
||||
Thread.attributes.categories.contains(cat.id),
|
||||
Thread.attributes.accountId.equal(cat.accountId),
|
||||
Thread.attributes.unread.equal(true),
|
||||
categoryAttribute.contains(cat.id)
|
||||
])
|
||||
|
||||
module.exports = new ThreadCountsStore
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class WorkspaceStore extends NylasStore
|
|||
account = FocusedPerspectiveStore.current()?.account
|
||||
category = CategoryStore.getStandardCategory(account, categoryName)
|
||||
return unless category
|
||||
view = MailboxPerspective.forCategory(account, category)
|
||||
view = MailboxPerspective.forCategory(category)
|
||||
return unless view
|
||||
Actions.focusMailboxPerspective(view)
|
||||
|
||||
|
|
@ -74,15 +74,15 @@ class WorkspaceStore extends NylasStore
|
|||
Actions.selectRootSheet(@Sheet.Drafts)
|
||||
|
||||
_selectAllView: ->
|
||||
account = FocusedPerspectiveStore.current()?.account
|
||||
category = CategoryStore.getArchiveCategory(account)
|
||||
return unless category
|
||||
view = MailboxPerspective.forCategory(account, category)
|
||||
return unless view
|
||||
accountIds = FocusedPerspectiveStore.current().accountIds
|
||||
categories = accountIds.map (aid) -> CategoryStore.getArchiveCategory(aid)
|
||||
|
||||
view = MailboxPerspective.forCategories(categories)
|
||||
Actions.focusMailboxPerspective(view)
|
||||
|
||||
_selectStarredView: ->
|
||||
Actions.focusMailboxPerspective MailboxPerspective.forStarred()
|
||||
accountIds = FocusedPerspectiveStore.current().accountIds
|
||||
Actions.focusMailboxPerspective MailboxPerspective.forStarred(accountIds)
|
||||
|
||||
_resetInstanceVars: =>
|
||||
@Location = Location = {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
_ = require 'underscore'
|
||||
Task = require './task'
|
||||
Folder = require '../models/folder'
|
||||
Thread = require '../models/thread'
|
||||
Message = require '../models/message'
|
||||
DatabaseStore = require '../stores/database-store'
|
||||
|
|
@ -30,9 +29,7 @@ class ChangeFolderTask extends ChangeMailTask
|
|||
"Moving to folder…"
|
||||
|
||||
description: ->
|
||||
folderText = ""
|
||||
if @folder instanceof Folder
|
||||
folderText = " to #{@folder.displayName}"
|
||||
folderText = " to #{@folder.displayName}"
|
||||
|
||||
if @threads.length > 0
|
||||
if @threads.length > 1
|
||||
|
|
@ -58,7 +55,7 @@ class ChangeFolderTask extends ChangeMailTask
|
|||
# Convert arrays of IDs or models to models.
|
||||
# modelify returns immediately if no work is required
|
||||
Promise.props(
|
||||
folder: DatabaseStore.modelify(Folder, [@folder])
|
||||
folder: DatabaseStore.modelify(Category, [@folder])
|
||||
threads: DatabaseStore.modelify(Thread, @threads)
|
||||
messages: DatabaseStore.modelify(Message, @messages)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
_ = require 'underscore'
|
||||
Task = require './task'
|
||||
Label = require '../models/label'
|
||||
Thread = require '../models/thread'
|
||||
Message = require '../models/message'
|
||||
DatabaseStore = require '../stores/database-store'
|
||||
|
|
@ -10,8 +9,8 @@ SyncbackCategoryTask = require './syncback-category-task'
|
|||
# Public: Create a new task to apply labels to a message or thread.
|
||||
#
|
||||
# Takes an options object of the form:
|
||||
# - labelsToAdd: An {Array} of {Label}s or {Label} ids to add
|
||||
# - labelsToRemove: An {Array} of {Label}s or {Label} ids to remove
|
||||
# - labelsToAdd: An {Array} of {Category}s or {Category} ids to add
|
||||
# - labelsToRemove: An {Array} of {Category}s or {Category} ids to remove
|
||||
# - threads: An {Array} of {Thread}s or {Thread} ids
|
||||
# - messages: An {Array} of {Message}s or {Message} ids
|
||||
class ChangeLabelsTask extends ChangeMailTask
|
||||
|
|
@ -28,9 +27,9 @@ class ChangeLabelsTask extends ChangeMailTask
|
|||
type = "thread"
|
||||
if @threads.length > 1
|
||||
type = "threads"
|
||||
if @labelsToAdd.length is 1 and @labelsToRemove.length is 0 and @labelsToAdd[0] instanceof Label
|
||||
if @labelsToAdd.length is 1 and @labelsToRemove.length is 0 and @labelsToAdd[0] instanceof Category
|
||||
return "Added #{@labelsToAdd[0].displayName} to #{@threads.length} #{type}"
|
||||
if @labelsToAdd.length is 0 and @labelsToRemove.length is 1 and @labelsToRemove[0] instanceof Label
|
||||
if @labelsToAdd.length is 0 and @labelsToRemove.length is 1 and @labelsToRemove[0] instanceof Category
|
||||
return "Removed #{@labelsToRemove[0].displayName} from #{@threads.length} #{type}"
|
||||
return "Changed labels on #{@threads.length} #{type}"
|
||||
|
||||
|
|
@ -50,8 +49,8 @@ class ChangeLabelsTask extends ChangeMailTask
|
|||
# Convert arrays of IDs or models to models.
|
||||
# modelify returns immediately if no work is required
|
||||
Promise.props(
|
||||
labelsToAdd: DatabaseStore.modelify(Label, @labelsToAdd)
|
||||
labelsToRemove: DatabaseStore.modelify(Label, @labelsToRemove)
|
||||
labelsToAdd: DatabaseStore.modelify(Category, @labelsToAdd)
|
||||
labelsToRemove: DatabaseStore.modelify(Category, @labelsToRemove)
|
||||
threads: DatabaseStore.modelify(Thread, @threads)
|
||||
messages: DatabaseStore.modelify(Message, @messages)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
DatabaseStore = require '../stores/database-store'
|
||||
Label = require '../models/label'
|
||||
Folder = require '../models/folder'
|
||||
AccountStore = require '../stores/account-store'
|
||||
Category = require '../models/category'
|
||||
Task = require './task'
|
||||
ChangeFolderTask = require './change-folder-task'
|
||||
ChangeLabelTask = require './change-labels-task'
|
||||
|
|
@ -14,11 +14,7 @@ class DestroyCategoryTask extends Task
|
|||
super
|
||||
|
||||
label: ->
|
||||
name = @category.displayName
|
||||
if @category instanceof Label
|
||||
"Deleting label #{name}..."
|
||||
else
|
||||
"Deleting folder #{name}..."
|
||||
"Deleting #{@category.displayType()} #{@category.displayName}..."
|
||||
|
||||
isDependentTask: (other) ->
|
||||
(other instanceof ChangeFolderTask) or
|
||||
|
|
@ -39,7 +35,7 @@ class DestroyCategoryTask extends Task
|
|||
if not @category.serverId
|
||||
return Promise.reject(new Error("Attempt to call DestroyCategoryTask.performRemote without @category.serverId."))
|
||||
|
||||
if @category instanceof Label
|
||||
if AccountStore.accountForId(@category.accountId).usesLabels()
|
||||
path = "/labels/#{@category.serverId}"
|
||||
else
|
||||
path = "/folders/#{@category.serverId}"
|
||||
|
|
@ -66,14 +62,12 @@ class DestroyCategoryTask extends Task
|
|||
else
|
||||
return Promise.resolve(Task.Status.Retry)
|
||||
|
||||
_displayType: ->
|
||||
|
||||
_notifyUserOfError: (category = @category) ->
|
||||
displayName = category.displayName
|
||||
displayType = if category instanceof Label
|
||||
'label'
|
||||
else
|
||||
'folder'
|
||||
|
||||
msg = "The #{displayType} #{displayName} could not be deleted."
|
||||
msg = "The #{category.displayType()} #{displayName} could not be deleted."
|
||||
if displayType is 'folder'
|
||||
msg += " Make sure the folder you want to delete is empty before deleting it."
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
CategoryStore = require '../stores/category-store'
|
||||
DatabaseStore = require '../stores/database-store'
|
||||
Label = require '../models/label'
|
||||
Folder = require '../models/folder'
|
||||
{generateTempId} = require '../models/utils'
|
||||
Task = require './task'
|
||||
NylasAPI = require '../nylas-api'
|
||||
|
|
@ -13,10 +11,7 @@ module.exports = class SyncbackCategoryTask extends Task
|
|||
super
|
||||
|
||||
label: ->
|
||||
if @category instanceof Label
|
||||
"Creating new label..."
|
||||
else
|
||||
"Creating new folder..."
|
||||
"Creating new #{@category.displayType()}..."
|
||||
|
||||
performLocal: ->
|
||||
# When we send drafts, we don't update anything in the app until
|
||||
|
|
@ -32,7 +27,7 @@ module.exports = class SyncbackCategoryTask extends Task
|
|||
t.persistModel @category
|
||||
|
||||
performRemote: ->
|
||||
if @category instanceof Label
|
||||
if AccountStore.accountForId(@category.accountId).usesLabels()
|
||||
path = "/labels"
|
||||
else
|
||||
path = "/folders"
|
||||
|
|
@ -51,7 +46,7 @@ module.exports = class SyncbackCategoryTask extends Task
|
|||
# created serverId.
|
||||
@category.serverId = json.id
|
||||
DatabaseStore.inTransaction (t) =>
|
||||
t.persistModel @category
|
||||
t.persistModel(@category)
|
||||
.then ->
|
||||
return Promise.resolve(Task.Status.Success)
|
||||
.catch APIError, (err) =>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
Rx = require 'rx-lite'
|
||||
_ = require 'underscore'
|
||||
Category = require '../flux/models/category'
|
||||
QuerySubscriptionPool = require '../flux/models/query-subscription-pool'
|
||||
AccountStore = require '../flux/stores/account-store'
|
||||
DatabaseStore = require '../flux/stores/database-store'
|
||||
|
|
@ -31,21 +32,15 @@ CategoryOperators =
|
|||
CategoryObservables =
|
||||
|
||||
forAllAccounts: =>
|
||||
observable = Rx.Observable.fromStore(AccountStore).flatMapLatest ->
|
||||
observables = AccountStore.accounts().map (account) ->
|
||||
categoryClass = account.categoryClass()
|
||||
Rx.Observable.fromQuery(DatabaseStore.findAll(categoryClass))
|
||||
Rx.Observable.concat(observables)
|
||||
observable = Rx.Observable.fromQuery(DatabaseStore.findAll(Category))
|
||||
_.extend(observable, CategoryOperators)
|
||||
observable
|
||||
|
||||
forAccount: (account) =>
|
||||
if account
|
||||
categoryClass = account.categoryClass()
|
||||
observable = Rx.Observable.fromQuery(DatabaseStore.findAll(categoryClass)
|
||||
.where(categoryClass.attributes.accountId.equal(account.id)))
|
||||
observable = Rx.Observable.fromQuery(DatabaseStore.findAll(Category).where(accountId: account.id))
|
||||
else
|
||||
observable = CategoryObservables.forAllAccounts()
|
||||
observable = Rx.Observable.fromQuery(DatabaseStore.findAll(Category))
|
||||
_.extend(observable, CategoryOperators)
|
||||
observable
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ _ = require 'underscore'
|
|||
|
||||
Task = require './flux/tasks/task'
|
||||
Actions = require './flux/actions'
|
||||
Label = require './flux/models/label'
|
||||
Folder = require './flux/models/folder'
|
||||
Category = require './flux/models/category'
|
||||
Thread = require './flux/models/thread'
|
||||
Message = require './flux/models/message'
|
||||
AccountStore = require './flux/stores/account-store'
|
||||
|
|
@ -25,7 +24,7 @@ information about the current view. Maybe after the unified inbox refactor...
|
|||
###
|
||||
MailRulesActions =
|
||||
markAsImportant: (message, thread) ->
|
||||
DatabaseStore.findBy(Label, {
|
||||
DatabaseStore.findBy(Category, {
|
||||
name: 'important',
|
||||
accountId: thread.accountId
|
||||
}).then (important) ->
|
||||
|
|
@ -33,10 +32,10 @@ MailRulesActions =
|
|||
return new ChangeLabelsTask(labelsToAdd: [important], threads: [thread])
|
||||
|
||||
moveToTrash: (message, thread) ->
|
||||
if AccountStore.accountForId(thread.accountId).categoryClass() is Label
|
||||
if AccountStore.accountForId(thread.accountId).usesLabels()
|
||||
return MailRulesActions._applyStandardLabelRemovingInbox(message, thread, 'trash')
|
||||
else
|
||||
DatabaseStore.findBy(Folder, { name: 'trash', accountId: thread.accountId }).then (folder) ->
|
||||
DatabaseStore.findBy(Category, { name: 'trash', accountId: thread.accountId }).then (folder) ->
|
||||
return Promise.reject(new Error("The folder could not be found.")) unless folder
|
||||
return new ChangeFolderTask(folder: folder, threads: [thread])
|
||||
|
||||
|
|
@ -48,13 +47,13 @@ MailRulesActions =
|
|||
|
||||
changeFolder: (message, thread, value) ->
|
||||
return Promise.reject(new Error("A folder is required.")) unless value
|
||||
DatabaseStore.findBy(Folder, { id: value, accountId: thread.accountId }).then (folder) ->
|
||||
DatabaseStore.findBy(Category, { id: value, accountId: thread.accountId }).then (folder) ->
|
||||
return Promise.reject(new Error("The folder could not be found.")) unless folder
|
||||
return new ChangeFolderTask(folder: folder, threads: [thread])
|
||||
|
||||
applyLabel: (message, thread, value) ->
|
||||
return Promise.reject(new Error("A label is required.")) unless value
|
||||
DatabaseStore.findBy(Label, { id: value, accountId: thread.accountId }).then (label) ->
|
||||
DatabaseStore.findBy(Category, { id: value, accountId: thread.accountId }).then (label) ->
|
||||
return Promise.reject(new Error("The label could not be found.")) unless label
|
||||
return new ChangeLabelsTask(labelsToAdd: [label], threads: [thread])
|
||||
|
||||
|
|
@ -65,8 +64,8 @@ MailRulesActions =
|
|||
|
||||
_applyStandardLabelRemovingInbox: (message, thread, value) ->
|
||||
Promise.props(
|
||||
inbox: DatabaseStore.findBy(Label, { name: 'inbox', accountId: thread.accountId })
|
||||
newLabel: DatabaseStore.findBy(Label, { name: value, accountId: thread.accountId })
|
||||
inbox: DatabaseStore.findBy(Category, { name: 'inbox', accountId: thread.accountId })
|
||||
newLabel: DatabaseStore.findBy(Category, { name: value, accountId: thread.accountId })
|
||||
).then ({inbox, newLabel}) ->
|
||||
return Promise.reject(new Error("Could not find `inbox` or `#{value}` label")) unless inbox and newLabel
|
||||
return new ChangeLabelsTask
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ Actions = require './flux/actions'
|
|||
class MailboxPerspective
|
||||
|
||||
# Factory Methods
|
||||
@forNothing: ->
|
||||
new EmptyMailboxPerspective()
|
||||
|
||||
@forCategory: (accountIds, category) ->
|
||||
@forCategory: (category) ->
|
||||
new CategoryMailboxPerspective([category])
|
||||
|
||||
@forCategories: (accountIds, categories) ->
|
||||
@forCategories: (categories) ->
|
||||
new CategoryMailboxPerspective(categories)
|
||||
|
||||
@forStarred: (accountIds) ->
|
||||
|
|
@ -29,171 +31,168 @@ class MailboxPerspective
|
|||
new SearchMailboxPerspective(accountIds, query)
|
||||
|
||||
@forAll: (accountIds) ->
|
||||
new AllMailboxPerspective(accountIds)
|
||||
|
||||
threads: ->
|
||||
matchers = [@matchers()]
|
||||
matchers.push Thread.attributes.accountId.in(@accountIds) if @accountIds
|
||||
|
||||
query = DatabaseStore.findAll(Thread).where(matchers).limit(0)
|
||||
return new MutableQuerySubscription(query, {asResultSet: true})
|
||||
categories = accountIds.map (aid) ->
|
||||
CategoryStore.getStandardCategory(aid, "all")
|
||||
new CategoryMailboxPerspective(_.compact(categories))
|
||||
|
||||
# Instance Methods
|
||||
|
||||
constructor: (@accountIds) ->
|
||||
unless @accountIds instanceof Array and _.every(@accountIds, _.isString)
|
||||
throw new Error("#{@constructor.name}: You must provide an array of string `accountIds`")
|
||||
@
|
||||
|
||||
isEqual: (other) ->
|
||||
return false unless other and @constructor.name is other.constructor.name
|
||||
isEqual: (other) =>
|
||||
return false unless other and @constructor is other.constructor
|
||||
return false unless other.name is @name
|
||||
return false unless _.isEqual(@accountIds, other.accountIds)
|
||||
|
||||
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.")
|
||||
categories: =>
|
||||
[]
|
||||
|
||||
matchers: ->
|
||||
throw new Error("matchers: Not implemented in base class.")
|
||||
threads: =>
|
||||
throw new Error("threads: Not implemented in base class.")
|
||||
|
||||
canApplyToThreads: ->
|
||||
canApplyToThreads: =>
|
||||
throw new Error("canApplyToThreads: Not implemented in base class.")
|
||||
|
||||
applyToThreads: (threadsOrIds) ->
|
||||
applyToThreads: (threadsOrIds) =>
|
||||
throw new Error("applyToThreads: Not implemented in base class.")
|
||||
|
||||
# Whether or not the current MailboxPerspective can "archive" or "trash"
|
||||
# Subclasses should call `super` if they override these methods
|
||||
canArchiveThreads: ->
|
||||
canArchiveThreads: =>
|
||||
for aid in @accountIds
|
||||
return false unless CategoryStore.getArchiveCategory(AccountStore.accountForId(aid))
|
||||
return true
|
||||
|
||||
canTrashThreads: ->
|
||||
canTrashThreads: =>
|
||||
for aid in @accountIds
|
||||
return false unless CategoryStore.getTrashCategory(AccountStore.accountForId(aid))
|
||||
return true
|
||||
|
||||
class SearchMailboxPerspective extends MailboxPerspective
|
||||
constructor: (@accountIds, @searchQuery) ->
|
||||
super(@accountIds)
|
||||
|
||||
unless _.isString(@searchQuery)
|
||||
throw new Error("SearchMailboxPerspective: Expected a `string` search query")
|
||||
|
||||
@
|
||||
|
||||
isEqual: (other) ->
|
||||
isEqual: (other) =>
|
||||
super(other) and other.searchQuery is @searchQuery
|
||||
|
||||
matchers: ->
|
||||
null
|
||||
|
||||
canApplyToThreads: ->
|
||||
false
|
||||
|
||||
canArchiveThreads: ->
|
||||
false
|
||||
|
||||
canTrashThreads: ->
|
||||
false
|
||||
|
||||
categoryIds: ->
|
||||
null
|
||||
|
||||
threads: ->
|
||||
threads: =>
|
||||
new SearchSubscription(@searchQuery, @accountIds)
|
||||
|
||||
class AllMailboxPerspective extends MailboxPerspective
|
||||
constructor: (@accountIds) ->
|
||||
@name = "All"
|
||||
@iconName = "all-mail.png"
|
||||
@
|
||||
|
||||
matchers: ->
|
||||
[Thread.attributes.accountId.in(@accountIds)]
|
||||
|
||||
canApplyToThreads: ->
|
||||
true
|
||||
|
||||
canArchiveThreads: ->
|
||||
canApplyToThreads: =>
|
||||
false
|
||||
|
||||
canTrashThreads: ->
|
||||
canArchiveThreads: =>
|
||||
false
|
||||
|
||||
categoryIds: ->
|
||||
@accountIds.map (aid) ->
|
||||
CategoryStore.getStandardCategory(AccountStore.accountForId(aid), "all")?.id
|
||||
canTrashThreads: =>
|
||||
false
|
||||
|
||||
|
||||
class StarredMailboxPerspective extends MailboxPerspective
|
||||
constructor: (@accountIds) ->
|
||||
super(@accountIds)
|
||||
@name = "Starred"
|
||||
@iconName = "starred.png"
|
||||
@
|
||||
|
||||
matchers: ->
|
||||
[Thread.attributes.starred.equal(true)]
|
||||
threads: =>
|
||||
query = DatabaseStore.findAll(Thread).where([
|
||||
Thread.attributes.accountId.in(@accountIds),
|
||||
Thread.attributes.starred.equal(true)
|
||||
]).limit(0)
|
||||
|
||||
categoryIds: ->
|
||||
null
|
||||
return new MutableQuerySubscription(query, {asResultSet: true})
|
||||
|
||||
canApplyToThreads: ->
|
||||
canApplyToThreads: =>
|
||||
true
|
||||
|
||||
applyToThreads: (threadsOrIds) ->
|
||||
applyToThreads: (threadsOrIds) =>
|
||||
ChangeStarredTask = require './flux/tasks/change-starred-task'
|
||||
task = new ChangeStarredTask({threads:threadsOrIds, starred: true})
|
||||
Actions.queueTask(task)
|
||||
|
||||
|
||||
class CategoryMailboxPerspective extends MailboxPerspective
|
||||
constructor: (@categories) ->
|
||||
unless @categories instanceof Array
|
||||
throw new Error("CategoryMailboxPerspective: You must provide a `categories` array")
|
||||
class EmptyMailboxPerspective extends MailboxPerspective
|
||||
constructor: ->
|
||||
|
||||
@accountIds = _.uniq(_.pluck(@categories, 'accountId'))
|
||||
threads: =>
|
||||
query = DatabaseStore.findAll(Thread).where(accountId: -1).limit(0)
|
||||
return new MutableQuerySubscription(query, {asResultSet: true})
|
||||
|
||||
canApplyToThreads: =>
|
||||
false
|
||||
|
||||
canArchiveThreads: =>
|
||||
false
|
||||
|
||||
canTrashThreads: =>
|
||||
false
|
||||
|
||||
applyToThreads: (threadsOrIds) =>
|
||||
|
||||
|
||||
class CategoryMailboxPerspective extends MailboxPerspective
|
||||
constructor: (@_categories) ->
|
||||
super(_.uniq(_.pluck(@_categories, 'accountId')))
|
||||
|
||||
if @_categories.length is 0
|
||||
throw new Error("CategoryMailboxPerspective: You must provide at least one category.")
|
||||
|
||||
# Note: We pick the display name and icon assuming that you won't create a
|
||||
# perspective with Inbox and Sent or anything crazy like that... todo?
|
||||
@name = @categories[0].displayName
|
||||
if @category[0].name
|
||||
@iconName = "#{@category[0].name}.png"
|
||||
@name = @_categories[0].displayName
|
||||
if @_categories[0].name
|
||||
@iconName = "#{@_categories[0].name}.png"
|
||||
else
|
||||
@iconName = CategoryHelpers.categoryIconName(@accountIds[0])
|
||||
|
||||
@
|
||||
|
||||
matchers: =>
|
||||
matchers.push Thread.attributes.labels.containsAny(@categoryIds())
|
||||
matchers
|
||||
isEqual: (other) =>
|
||||
super(other) and _.isEqual(@categories(), other.categories())
|
||||
|
||||
categoryIds: ->
|
||||
_.pluck(@categories, 'id')
|
||||
threads: =>
|
||||
query = DatabaseStore
|
||||
.findAll(Thread)
|
||||
.where([Thread.attributes.categories.containsAny(_.pluck(@categories(), 'id'))])
|
||||
.limit(0)
|
||||
|
||||
canApplyToThreads: ->
|
||||
not _.any @categories, (c) -> c.isLockedCategory()
|
||||
query.distinct() if @categories().length > 1
|
||||
|
||||
canArchiveThreads: ->
|
||||
for cat in @categories
|
||||
return new MutableQuerySubscription(query, {asResultSet: true})
|
||||
|
||||
categories: =>
|
||||
@_categories
|
||||
|
||||
canApplyToThreads: =>
|
||||
not _.any @_categories, (c) -> c.isLockedCategory()
|
||||
|
||||
canArchiveThreads: =>
|
||||
for cat in @_categories
|
||||
return false if cat.name in ["archive", "all", "sent"]
|
||||
super
|
||||
|
||||
canTrashThreads: ->
|
||||
for cat in @categories
|
||||
canTrashThreads: =>
|
||||
for cat in @_categories
|
||||
return false if cat.name in ["trash"]
|
||||
super
|
||||
|
||||
applyToThreads: (threadsOrIds) ->
|
||||
applyToThreads: (threadsOrIds) =>
|
||||
# TODO:
|
||||
# categoryToApplyForAccount = {}
|
||||
# for cat in @categories
|
||||
# for cat in @_categories
|
||||
# categoryToApplyForAccount[cat.accountId] = cat
|
||||
#
|
||||
# @categories.forEach (cat) ->
|
||||
# @_categories.forEach (cat) ->
|
||||
#
|
||||
# if @account.usesLabels()
|
||||
# FocusedPerspectiveStore = require './flux/stores/focused-perspective-store'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue