mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-06 04:35:30 +08:00
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:
parent
c597a72c28
commit
910f272076
15 changed files with 188 additions and 23 deletions
|
@ -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"
|
||||
|
|
|
@ -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) }/>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) }>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
55
src/components/mail-important-icon.cjsx
Normal file
55
src/components/mail-important-icon.cjsx
Normal 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
|
|
@ -10,6 +10,9 @@ module.exports =
|
|||
showUnreadBadge:
|
||||
type: 'boolean'
|
||||
default: true
|
||||
showImportant:
|
||||
type: 'boolean'
|
||||
default: true
|
||||
disabledPackages:
|
||||
type: 'array'
|
||||
default: []
|
||||
|
|
|
@ -66,4 +66,7 @@ class Account extends Model
|
|||
return 'Gmail'
|
||||
return @provider
|
||||
|
||||
usesImportantFlag: ->
|
||||
@provider is 'gmail'
|
||||
|
||||
module.exports = Account
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
BIN
static/images/important/Icon-Important-Active@2x.png
Normal file
BIN
static/images/important/Icon-Important-Active@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 KiB |
BIN
static/images/important/Icon-Important-Hover@2x.png
Normal file
BIN
static/images/important/Icon-Important-Hover@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 KiB |
BIN
static/images/important/Icon-Important-HoverActive@2x.png
Normal file
BIN
static/images/important/Icon-Important-HoverActive@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 KiB |
Loading…
Add table
Reference in a new issue