feat(important): Improtant flags you can enable optionally for Gmail accounts

Summary: Fixes T3477

Test Plan: Tests are forthcoming

Reviewers: dillon, evan

Reviewed By: evan

Maniphest Tasks: T3477

Differential Revision: https://phab.nylas.com/D1990
This commit is contained in:
Ben Gotow 2015-09-08 10:53:07 -07:00
parent c597a72c28
commit 910f272076
15 changed files with 188 additions and 23 deletions

View file

@ -33,6 +33,7 @@ class NylasComponentKit
@loadFrom "MailLabel", "mail-label"
@loadFrom "LabelColorizer", "mail-label"
@load "MailImportantIcon", 'mail-important-icon'
@loadFrom "FormItem", "generated-form"
@loadFrom "GeneratedForm", "generated-form"

View file

@ -20,6 +20,7 @@ MessageItemContainer = require './message-item-container'
RetinaImg,
InjectedComponentSet,
MailLabel,
MailImportantIcon,
InjectedComponent} = require('nylas-component-kit')
class MessageListScrollTooltip extends React.Component
@ -218,13 +219,18 @@ class MessageList extends React.Component
</div>
_renderSubject: ->
subject = @state.currentThread?.subject
subject = "(No Subject)" if not subject or subject.length is 0
<div className="message-subject-wrap">
<span className="message-subject">{@state.currentThread?.subject}</span>
<MailImportantIcon thread={@state.currentThread} />
<span className="message-subject">{subject}</span>
{@_renderLabels()}
</div>
_renderLabels: =>
labels = @state.currentThread.sortedLabels()
labels = _.reject labels, (l) -> l.name is 'important'
labels.map (label) =>
<MailLabel label={label} key={label.id} onRemove={ => @_onRemoveLabel(label) }/>

View file

@ -68,6 +68,7 @@
display: flex;
flex-direction: column;
position: relative;
background: @background-secondary;
width: 100%;
height: 100%;
@ -83,6 +84,10 @@
-webkit-user-select: text;
line-height: @font-size-large * 1.8;
}
.mail-important-icon {
margin-right:@spacing-half;
margin-bottom:2px;
}
.message-subject {
font-size: @font-size-large;
color: @text-color;
@ -109,7 +114,6 @@
flex: 1;
opacity:0;
transition: opacity 0s;
background: @background-secondary;
}
.messages-wrap.ready {
opacity:1;

View file

@ -1,7 +1,7 @@
React = require 'react'
_ = require 'underscore'
{RetinaImg, Flexbox} = require 'nylas-component-kit'
{LaunchServices} = require 'nylas-exports'
{LaunchServices, AccountStore} = require 'nylas-exports'
class PreferencesGeneral extends React.Component
@displayName: 'PreferencesGeneral'
@ -22,15 +22,30 @@ class PreferencesGeneral extends React.Component
@setState(defaultClient: true)
@_services.registerForURLScheme('mailto')
toggleShowImportant: (event) =>
@props.config.toggle('core.showImportant')
event.preventDefault()
_renderImportanceOptionElement: =>
return false unless AccountStore.current().usesImportantFlag()
importanceOptionElement = <div className="section-header">
<input type="checkbox" id="show-important"
checked={@props.config.get('core.showImportant')}
onChange={@toggleShowImportant}/>
<label htmlFor="show-important">Show Gmail-style important markers</label>
</div>
render: =>
<div className="container-notifications">
<div className="section">
<div className="section-header platform-darwin-only" style={marginBottom:30}>
<div className="section-header platform-darwin-only">
<input type="checkbox" id="default-client" checked={@state.defaultClient} onChange={@toggleDefaultMailClient}/>
<label htmlFor="default-client">Use Nylas as my default mail client</label>
</div>
<div className="section-header">
{@_renderImportanceOptionElement()}
<div className="section-header" style={marginTop:30}>
Delay for marking messages as read:
<select value={@props.config.get('core.reading.markAsReadDelay')}
onChange={ (event) => @props.config.set('core.reading.markAsReadDelay', event.target.value) }>

View file

@ -30,7 +30,7 @@ class ThreadListIcon extends React.Component
else
return 'thread-icon-replied thread-icon-star-on-hover'
return 'thread-icon-star-on-hover'
return 'thread-icon-none thread-icon-star-on-hover'
_nonDraftMessages: =>
msgs = @props.thread.metadata

View file

@ -18,6 +18,7 @@ ThreadListStore = require './thread-list-store'
ThreadListIcon = require './thread-list-icon'
EmptyState = require './empty-state'
{MailImportantIcon} = require 'nylas-component-kit'
class ThreadListScrollTooltip extends React.Component
@displayName: 'ThreadListScrollTooltip'
@ -65,7 +66,10 @@ class ThreadList extends React.Component
c1 = new ListTabular.Column
name: "★"
resolver: (thread) =>
<ThreadListIcon thread={thread} />
<span>
<ThreadListIcon thread={thread} />
<MailImportantIcon thread={thread} />
</span>
c2 = new ListTabular.Column
name: "Participants"
@ -96,12 +100,13 @@ class ThreadList extends React.Component
currentCategoryId = FocusedMailViewStore.mailView()?.categoryId()
allCategoryId = CategoryStore.getStandardCategory('all')?.id
ignoredIds = [currentCategoryId, allCategoryId]
ignoredIds = [currentCategoryId]
ignoredIds.push(cat.id) for cat in CategoryStore.getHiddenCategories()
for label in (thread.sortedLabels())
continue if label.id in ignoredIds
if not c3LabelComponentCache[label.id]
c3LabelComponentCache[label.id] = <MailLabel label={label} key={label.id} />
c3LabelComponentCache[label.id] ?= <MailLabel label={label} key={label.id} />
labels.push c3LabelComponentCache[label.id]
<span className="details">
@ -147,6 +152,7 @@ class ThreadList extends React.Component
{attachment}
<span className="timestamp">{timestamp(thread.lastMessageReceivedTimestamp)}</span>
</div>
<MailImportantIcon thread={thread} />
<div className="subject">{subject(thread.subject)}</div>
<div className="snippet">{thread.snippet}</div>
</div>

View file

@ -22,7 +22,7 @@
color: @text-color-inverse-subtle;
}
.thread-icon, .draft-icon {
.thread-icon, .draft-icon, .mail-important-icon {
-webkit-filter: brightness(600%) grayscale(100%);
}
@ -58,6 +58,16 @@
border-bottom: 1px solid fade(@list-border, 60%);
}
.mail-important-icon {
margin-top:1px;
margin-left:6px;
padding: 12px;
vertical-align: initial;
&:not(.active) {
visibility: hidden;
}
}
.message-count {
color: @text-color-inverse;
background: @background-tertiary;
@ -157,6 +167,7 @@
width:15px;
height:15px;
background-size: 15px;
display:inline-block;
background-repeat: no-repeat;
background-position:center;
padding:12px;
@ -261,10 +272,18 @@
// stars
.thread-list .thread-icon-star:hover {
.thread-list .thread-icon-star:hover,
.thread-list .list-item:hover .thread-icon-none:hover {
background-image:url(../static/images/thread-list/icon-star-action-hover-@2x.png);
background-size: 16px;
}
.thread-list .list-item:hover .thread-icon-none {
background-image:url(../static/images/thread-list/icon-star-hover-@2x.png);
background-size: 16px;
}
.thread-list .list-item:hover .mail-important-icon {
visibility: inherit;
}
.thread-list .thread-icon-star-on-hover:hover {
background-image:url(../static/images/thread-list/icon-star-hover-@2x.png);
background-size: 16px;
@ -280,6 +299,15 @@
.thread-icon {
margin-right:6px;
}
.mail-important-icon {
margin-top:1px;
margin-left:1px;
float:left;
padding: 12px;
vertical-align: initial;
}
.subject {
font-size: @font-size-base;
overflow: hidden;

View file

@ -0,0 +1,55 @@
_ = require 'underscore'
React = require 'react'
{Actions,
Utils,
Thread,
ChangeLabelsTask,
CategoryStore,
AccountStore} = require 'nylas-exports'
class MailImportantIcon extends React.Component
@displayName: 'MailImportantIcon'
@propTypes:
thread: React.PropTypes.object
constructor: (@props) ->
@state = @getStateFromStores()
getStateFromStores: =>
showing: AccountStore.current().usesImportantFlag() and atom.config.get('core.showImportant')
componentDidMount: =>
@subscription = atom.config.observe 'core.showImportant', =>
@setState(@getStateFromStores())
componentWillUnmount: =>
@subscription?.dispose()
shouldComponentUpdate: (nextProps, nextState) =>
return false if nextProps.thread is @props.thread and @state.showing is nextState.showing
true
render: =>
return false unless @state.showing
importantId = CategoryStore.getStandardCategory('important').id
isImportant = _.findWhere(@props.thread.labels, {id: importantId})?
activeClassname = if isImportant then "active" else ""
<div className="mail-important-icon #{activeClassname}" onClick={@_onToggleImportant}></div>
_onToggleImportant: (event) =>
importantLabel = CategoryStore.getStandardCategory('important')
isImportant = _.findWhere(@props.thread.labels, {id: importantLabel.id})?
if isImportant
task = new ChangeLabelsTask(thread: @props.thread, labelsToRemove: [importantLabel], labelsToAdd: [])
else
task = new ChangeLabelsTask(thread: @props.thread, labelsToAdd: [importantLabel], labelsToRemove: [])
Actions.queueTask(task)
# Don't trigger the thread row click
event.stopPropagation()
module.exports = MailImportantIcon

View file

@ -10,6 +10,9 @@ module.exports =
showUnreadBadge:
type: 'boolean'
default: true
showImportant:
type: 'boolean'
default: true
disabledPackages:
type: 'array'
default: []

View file

@ -66,4 +66,7 @@ class Account extends Model
return 'Gmail'
return @provider
usesImportantFlag: ->
@provider is 'gmail'
module.exports = Account

View file

@ -9,8 +9,14 @@ AccountStore = require './account-store'
class CategoryStore extends NylasStore
constructor: ->
@_categoryCache = {}
@_standardCategories = []
@_userCategories = []
@_hiddenCategories = []
@listenTo DatabaseStore, @_onDBChanged
@listenTo AccountStore, @_refreshCacheFromDB
atom.config.observe 'core.showImportant', => @_refreshCacheFromDB()
@_refreshCacheFromDB()
# We look for a few standard categories and display them in the Mailboxes
@ -37,6 +43,7 @@ class CategoryStore extends NylasStore
"all"
"archive"
"starred"
"important"
]
AllMailName: "all"
@ -69,7 +76,6 @@ class CategoryStore extends NylasStore
#
getCategories: -> _.values @_categoryCache
# Public: Returns the Folder or Label object for a standard category name.
# ('inbox', 'drafts', etc.) It's possible for this to return `null`.
# For example, Gmail likely doesn't have an `archive` label.
@ -82,25 +88,20 @@ class CategoryStore extends NylasStore
# Public: Returns all of the standard categories for the current account.
#
getStandardCategories: ->
# Single pass to create lookup table, single pass to get ordered array
byStandardName = {}
for key, val of @_categoryCache
byStandardName[val.name] = val
_.compact @StandardCategoryNames.map (name) =>
byStandardName[name]
@_standardCategories
getUnhiddenStandardCategories: ->
@getStandardCategories().filter (c) ->
not _.contains @HiddenCategoryNames, c.name
getHiddenCategories: ->
@_hiddenCategories
# Public: Returns all of the categories that are not part of the standard
# category set.
#
getUserCategories: ->
userCategories = _.reject _.values(@_categoryCache), (cat) =>
cat.name in @StandardCategoryNames or cat.name in @HiddenCategoryNames
userCategories = _.sortBy(userCategories, 'displayName')
return _.compact(userCategories)
@_userCategories
_onDBChanged: (change) ->
categoryClass = @categoryClass()
@ -117,6 +118,29 @@ class CategoryStore extends NylasStore
DatabaseStore.findAll(categoryClass).where(categoryClass.attributes.accountId.equal(account.id)).then (categories=[]) =>
@_categoryCache = {}
@_categoryCache[category.id] = category for category in categories
# Compute user categories
userCategories = _.reject _.values(@_categoryCache), (cat) =>
cat.name in @StandardCategoryNames or cat.name in @HiddenCategoryNames
userCategories = _.sortBy(userCategories, 'displayName')
@_userCategories = _.compact(userCategories)
# Compute hidden categories
@_hiddenCategories = _.filter _.values(@_categoryCache), (cat) =>
cat.name in @HiddenCategoryNames
# Compute standard categories
# Single pass to create lookup table, single pass to get ordered array
byStandardName = {}
for key, val of @_categoryCache
byStandardName[val.name] = val
if not atom.config.get('core.showImportant')
delete byStandardName['important']
@_standardCategories = _.compact @StandardCategoryNames.map (name) =>
byStandardName[name]
@trigger()
module.exports = new CategoryStore()

View file

@ -61,3 +61,23 @@
opacity: 1;
}
}
.mail-important-icon {
width:16px;
height:16px;
vertical-align: middle;
display:inline-block;
background-repeat: no-repeat;
background-position:center;
background-image:url(../static/images/important/Icon-Important-Hover@2x.png);
background-size: 16px;
}
.mail-important-icon.active {
background-image:url(../static/images/important/Icon-Important-Active@2x.png);
background-size: 16px;
}
.mail-important-icon:hover {
background-image:url(../static/images/important/Icon-Important-HoverActive@2x.png);
background-size: 16px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB