_ = require 'underscore' React = require 'react' {Utils, Thread, Actions, TaskQueue, CategoryStore, NamespaceStore, WorkspaceStore, ChangeLabelsTask, ChangeFolderTask, FocusedContentStore, FocusedCategoryStore} = require 'nylas-exports' {Menu, Popover, RetinaImg, 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 NamespaceStore.listen @_onStoreChanged @unsubscribers.push FocusedContentStore.listen @_onStoreChanged @unsubscribers.push FocusedCategoryStore.listen @_onStoreChanged @_commandUnsubscriber = atom.commands.add 'body', "application:change-category": @_onOpenCategoryPopover # 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 @_commandUnsubscriber.dispose() render: => return unless @_namespace if @_namespace?.usesLabels() img = "ic-toolbar-tag.png" tooltip = "Apply Labels" placeholder = "Label as" else if @_namespace?.usesFolders() img = "ic-toolbar-movetofolder.png" tooltip = "Move to Folder" placeholder = "Move to folder" else img = "" tooltip = "" placeholder = "" if @state.isPopoverOpen then tooltip = "" button = headerComponents = [ ] categoryDatum.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: (categoryDatum) => if categoryDatum.divider return if @_namespace?.usesLabels() icon = @_renderCheckbox(categoryDatum) else if @_namespace?.usesFolders() icon = @_renderFolderIcon(categoryDatum) else return
{icon}
{@_renderBoldedSearchResults(categoryDatum)}
_renderCheckbox: (categoryDatum) -> styles = {} styles.backgroundColor = categoryDatum.backgroundColor if categoryDatum.usage is 0 checkStatus = else if categoryDatum.usage < categoryDatum.numThreads checkStatus = @_onSelectCategory(categoryDatum)}/> else checkStatus = @_onSelectCategory(categoryDatum)}/>
@_onSelectCategory(categoryDatum)}/> {checkStatus}
_renderFolderIcon: (categoryDatum) -> _renderBoldedSearchResults: (categoryDatum) -> name = categoryDatum.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: (categoryDatum) => return unless @_threads().length > 0 return unless @_namespace @refs.menu.setSelectedItem(null) if @_namespace.usesLabels() if categoryDatum.usage > 0 task = new ChangeLabelsTask labelsToRemove: [categoryDatum.id] threadIds: @_threadIds() else task = new ChangeLabelsTask labelsToAdd: [categoryDatum.id] threadIds: @_threadIds() else if @_namespace.usesFolders() task = new ChangeFolderTask folderOrId: categoryDatum.id threadIds: @_threadIds() if @props.thread Actions.moveThread(@props.thread, task) else if @props.items Actions.moveThreads(@_threads(), task) else throw new Error("Invalid organizationUnit") @refs.popover.close() TaskQueue.enqueue(task) _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} @_namespace = NamespaceStore.current() return unless @_namespace categories = [].concat(CategoryStore.getStandardCategories()) .concat([{divider: true}]) .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(@_extendCategoryWithDisplayData, displayData)) .value() 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 = FocusedCategoryStore.categoryId() if @_namespace?.usesLabels() hiddenCategories = ["all", "spam", "trash", "drafts", "sent"] if allInInbox hiddenCategories.push("inbox") return false if category.divider else if @_namespace?.usesFolders() hiddenCategories = ["drafts", "sent"] return (category.name not in hiddenCategories) and (category.id isnt currentCategoryId) _allInInbox: (usageCount, numThreads) -> inbox = CategoryStore.getStandardCategory("inbox") return usageCount[inbox.id] is numThreads _extendCategoryWithDisplayData: ({usageCount, numThreads}, category) -> return category if category.divider cat = category.toJSON() usage = usageCount[cat.id] ? 0 cat.backgroundColor = LabelColorizer.backgroundColorDark(category) cat.usage = usage cat.numThreads = numThreads return cat _threadCategories: (thread) => if @_namespace.usesLabels() return (thread.labels ? []) else if @_namespace.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 [] _threadIds: => @_threads().map (thread) -> thread.id module.exports = CategoryPicker