_ = require 'underscore' React = require 'react' {Utils, Label, Folder, Thread, Actions, TaskQueue, TaskFactory, AccountStore, CategoryStore, DatabaseStore, WorkspaceStore, SyncbackCategoryTask, TaskQueueStatusStore, FocusedMailViewStore} = require 'nylas-exports' {Menu, Popover, RetinaImg, KeyCommandsRegion, LabelColorizer} = require 'nylas-component-kit' # This changes the category on one or more threads. class CategoryPicker extends React.Component @displayName: "CategoryPicker" @containerRequired: false constructor: (@props) -> @state = _.extend @_recalculateState(@props), searchValue: "" @contextTypes: sheetDepth: React.PropTypes.number componentDidMount: => @unsubscribers = [] @unsubscribers.push CategoryStore.listen @_onStoreChanged @unsubscribers.push AccountStore.listen @_onStoreChanged # If the threads we're picking categories for change, (like when they # get their categories updated), we expect our parents to pass us new # props. We don't listen to the DatabaseStore ourselves. componentWillReceiveProps: (nextProps) -> @setState @_recalculateState(nextProps) componentWillUnmount: => return unless @unsubscribers unsubscribe() for unsubscribe in @unsubscribers _keymapHandlers: -> "application:change-category": @_onOpenCategoryPopover render: => return unless @_account if @_account?.usesLabels() img = "toolbar-tag.png" tooltip = "Apply Labels" placeholder = "Label as" else if @_account?.usesFolders() img = "toolbar-movetofolder.png" tooltip = "Move to Folder" placeholder = "Move to folder" else img = "" tooltip = "" placeholder = "" if @state.isPopoverOpen then tooltip = "" button = headerComponents = [ ] item.id } itemContent={@_renderItemContent} onSelect={@_onSelectCategory} defaultSelectedIndex={if @state.searchValue is "" then -1 else 0} /> _onOpenCategoryPopover: => return unless @_threads().length > 0 return unless @context.sheetDepth is WorkspaceStore.sheetStack().length - 1 @refs.popover.open() return _renderItemContent: (item) => if item.divider return else if item.newCategoryItem return @_renderCreateNewItem(item) if @_account?.usesLabels() icon = @_renderCheckbox(item) else if @_account?.usesFolders() icon = @_renderFolderIcon(item) else return
{icon}
{@_renderBoldedSearchResults(item)}
_renderCreateNewItem: ({searchValue, name}) => if @_account?.usesLabels() picName = "tag" else if @_account?.usesFolders() picName = "folder"
“{searchValue}” (create new)
_renderCheckbox: (item) -> styles = {} styles.backgroundColor = item.backgroundColor if item.usage is 0 checkStatus = else if item.usage < item.numThreads checkStatus = @_onSelectCategory(item)}/> else checkStatus = @_onSelectCategory(item)}/>
@_onSelectCategory(item)}/> {checkStatus}
_renderFolderIcon: (item) -> _renderBoldedSearchResults: (item) -> name = item.display_name searchTerm = (@state.searchValue ? "").trim() return name if searchTerm.length is 0 re = Utils.wordSearchRegExp(searchTerm) parts = name.split(re).map (part) -> # The wordSearchRegExp looks for a leading non-word character to # deterine if it's a valid place to search. As such, we need to not # include that leading character as part of our match. if re.test(part) if /\W/.test(part[0]) return {part[0]}{part[1..-1]} else return {part} else return part return {parts} _onSelectCategory: (item) => threads = @_threads() return unless threads.length > 0 return unless @_account @refs.menu.setSelectedItem(null) if item.newCategoryItem CategoryClass = AccountStore.current().categoryClass() category = new CategoryClass displayName: @state.searchValue, accountId: AccountStore.current().id syncbackTask = new SyncbackCategoryTask({category}) TaskQueueStatusStore.waitForPerformRemote(syncbackTask).then => DatabaseStore.findBy(category.constructor, clientId: category.clientId).then (category) => applyTask = TaskFactory.taskForApplyingCategory threads: threads category: category Actions.queueTask(applyTask) Actions.queueTask(syncbackTask) else if item.usage is threads.length applyTask = TaskFactory.taskForRemovingCategory threads: threads category: item.category Actions.queueTask(applyTask) else applyTask = TaskFactory.taskForApplyingCategory threads: threads category: item.category Actions.queueTask(applyTask) @refs.popover.close() _onStoreChanged: => @setState @_recalculateState(@props) _onSearchValueChange: (event) => @setState @_recalculateState(@props, searchValue: event.target.value) _onPopoverOpened: => @setState @_recalculateState(@props, searchValue: "") @setState isPopoverOpen: true _onPopoverClosed: => @setState isPopoverOpen: false _recalculateState: (props=@props, {searchValue}={}) => searchValue = searchValue ? @state?.searchValue ? "" numThreads = @_threads(props).length if numThreads is 0 return {categoryData: [], searchValue} @_account = AccountStore.current() return unless @_account categories = CategoryStore.getStandardCategories() .concat([{divider: true, id: "category-divider"}]) .concat(CategoryStore.getUserCategories()) usageCount = @_categoryUsageCount(props, categories) allInInbox = @_allInInbox(usageCount, numThreads) displayData = {usageCount, numThreads} categoryData = _.chain(categories) .filter(_.partial(@_isUserFacing, allInInbox)) .filter(_.partial(@_isInSearch, searchValue)) .map(_.partial(@_itemForCategory, displayData)) .value() if searchValue.length > 0 newItemData = searchValue: searchValue newCategoryItem: true id: "category-create-new" categoryData.push(newItemData) return {categoryData, searchValue} _categoryUsageCount: (props, categories) => categoryUsageCount = {} _.flatten(@_threads(props).map(@_threadCategories)).forEach (category) -> categoryUsageCount[category.id] ?= 0 categoryUsageCount[category.id] += 1 return categoryUsageCount _isInSearch: (searchValue, category) -> return Utils.wordSearchRegExp(searchValue).test(category.displayName) _isUserFacing: (allInInbox, category) => hiddenCategories = [] currentCategoryId = FocusedMailViewStore.mailView()?.categoryId() if @_account?.usesLabels() hiddenCategories = ["all", "spam", "trash", "drafts", "sent"] if allInInbox hiddenCategories.push("inbox") return false if category.divider else if @_account?.usesFolders() hiddenCategories = ["drafts", "sent"] return (category.name not in hiddenCategories) and (category.id isnt currentCategoryId) _allInInbox: (usageCount, numThreads) -> inbox = CategoryStore.getStandardCategory("inbox") return false unless inbox return usageCount[inbox.id] is numThreads _itemForCategory: ({usageCount, numThreads}, category) -> return category if category.divider item = category.toJSON() item.category = category item.backgroundColor = LabelColorizer.backgroundColorDark(category) item.usage = usageCount[category.id] ? 0 item.numThreads = numThreads item _threadCategories: (thread) => if @_account.usesLabels() return (thread.labels ? []) else if @_account.usesFolders() return (thread.folders ? []) else throw new Error("Invalid organizationUnit") _threads: (props = @props) => if props.items then return (props.items ? []) else if props.thread then return [props.thread] else return [] module.exports = CategoryPicker