diff --git a/internal_packages/account-sidebar/lib/account-sidebar.cjsx b/internal_packages/account-sidebar/lib/account-sidebar.cjsx
index c4bf922f0..d134450f0 100644
--- a/internal_packages/account-sidebar/lib/account-sidebar.cjsx
+++ b/internal_packages/account-sidebar/lib/account-sidebar.cjsx
@@ -51,4 +51,4 @@ AccountSidebar = React.createClass
AccountSidebar.minWidth = 165
-AccountSidebar.maxWidth = 250
+AccountSidebar.maxWidth = 190
diff --git a/internal_packages/composer/lib/composer-view.cjsx b/internal_packages/composer/lib/composer-view.cjsx
index 3913eb1c0..566c4939e 100644
--- a/internal_packages/composer/lib/composer-view.cjsx
+++ b/internal_packages/composer/lib/composer-view.cjsx
@@ -31,6 +31,9 @@ ComposerView = React.createClass
bcc: []
body: ""
subject: ""
+ showcc: false
+ showbcc: false
+ showsubject: false
showQuotedText: false
isSending: DraftStore.sendingState(@props.localId)
state
@@ -41,8 +44,7 @@ ComposerView = React.createClass
FooterComponents: ComponentRegistry.findAllByRole 'Composer:Footer'
componentWillMount: ->
- @_prepareForDraft()
- # @_checkForKnownFrames()
+ @_prepareForDraft(@props.localId)
componentDidMount: ->
@_draftStoreUnlisten = DraftStore.listen @_onSendingStateChanged
@@ -64,9 +66,6 @@ ComposerView = React.createClass
@_draftStoreUnlisten() if @_draftStoreUnlisten
@keymap_unsubscriber.dispose()
- componentWillUpdate: ->
- #@_checkForKnownFrames()
-
componentDidUpdate: ->
# We want to use a temporary variable instead of putting this into the
# state. This is because the selection is a transient property that
@@ -76,28 +75,31 @@ ComposerView = React.createClass
@_recoveredSelection = null if @_recoveredSelection?
componentWillReceiveProps: (newProps) ->
- if newProps.localId != @props.localId
+ if newProps.localId isnt @props.localId
# When we're given a new draft localId, we have to stop listening to our
# current DraftStoreProxy, create a new one and listen to that. The simplest
# way to do this is to just re-call registerListeners.
@_teardownForDraft()
- @_prepareForDraft()
-
- _prepareForDraft: ->
- # UndoManager must be ready before we call _onDraftChanged for the first time
- @undoManager = new UndoManager
- @_proxy = DraftStore.sessionForLocalId(@props.localId)
- if @_proxy.draft()
- @_onDraftChanged()
+ @_prepareForDraft(newProps.localId)
+ _prepareForDraft: (localId) ->
@unlisteners = []
- @unlisteners.push @_proxy.listen(@_onDraftChanged)
@unlisteners.push ComponentRegistry.listen (event) =>
@setState(@getComponentRegistryState())
+ return unless localId
+
+ # UndoManager must be ready before we call _onDraftChanged for the first time
+ @undoManager = new UndoManager
+ @_proxy = DraftStore.sessionForLocalId(localId)
+ @unlisteners.push @_proxy.listen(@_onDraftChanged)
+ if @_proxy.draft()
+ @_onDraftChanged()
+
_teardownForDraft: ->
unlisten() for unlisten in @unlisteners
- @_proxy.changes.commit()
+ if @_proxy
+ @_proxy.changes.commit()
render: ->
if @props.mode is "inline"
@@ -210,10 +212,10 @@ ComposerView = React.createClass
data-tooltip="Attach file"
onClick={@_attachFile}>
-
+ onClick={@_sendDraft}> Send
{@_actionButtonComponents()}
@@ -241,10 +243,12 @@ ComposerView = React.createClass
Utils.isForwardedMessage(draft.body, draft.subject)
_actionButtonComponents: ->
+ return [] unless @props.localId
(@state.ActionButtonComponents ? []).map ({view, name}) =>
_footerComponents: ->
+ return [] unless @props.localId
(@state.FooterComponents ? []).map ({view, name}) =>
@@ -302,10 +306,12 @@ ComposerView = React.createClass
@setState showQuotedText: showQuotedText
_addToProxy: (changes={}, source={}) ->
+ return unless @_proxy
+
selections = @_getSelections()
oldDraft = @_proxy.draft()
- return if _.all changes, (change, key) -> change == oldDraft[key]
+ return if _.all changes, (change, key) -> _.isEqual(change, oldDraft[key])
@_proxy.changes.add(changes)
@_saveToHistory(selections) unless source.fromUndoManager
@@ -377,23 +383,10 @@ ComposerView = React.createClass
@setState {showcc: true}
@focus "textFieldCc"
- # Warning this method makes optimistic assumptions about the mail client
- # and is not properly encapsulated.
- _checkForKnownFrames: ->
- @_precalcComposerCss = {}
- mwrap = document.getElementsByClassName("messages-wrap")[0]
- if mwrap?
- INLINE_COMPOSER_OTHER_HEIGHT = 192
- mheight = mwrap.getBoundingClientRect().height
- @_precalcComposerCss =
- minHeight: mheight - INLINE_COMPOSER_OTHER_HEIGHT
-
_onSendingStateChanged: ->
@setState isSending: DraftStore.sendingState(@props.localId)
-
-
undo: (event) ->
event.preventDefault()
event.stopPropagation()
diff --git a/internal_packages/composer/lib/main.cjsx b/internal_packages/composer/lib/main.cjsx
index 506ef1534..796ee6a20 100644
--- a/internal_packages/composer/lib/main.cjsx
+++ b/internal_packages/composer/lib/main.cjsx
@@ -29,16 +29,16 @@ module.exports =
@item.setAttribute("class", "composer-full-window")
document.body.appendChild(@item)
+ component = React.render(, @item)
+
# Wait for the remaining state to be passed into the window
# from our parent. We need to wait for state because the windows are
# preloaded so they open instantly, so we don't have data initially
ipc.on 'composer-state', (optionsJSON) =>
options = JSON.parse(optionsJSON)
@_createDraft(options).then (draftLocalId) =>
- React.render(, @item)
- _.delay =>
- if options.error? then @_showInitialErrorDialog(options.error)
- , 100
+ component.setProps {localId: draftLocalId}, =>
+ @_showInitialErrorDialog(options.error) if options.error?
.catch (error) -> console.error(error)
@@ -71,6 +71,7 @@ module.exports =
from: [NamespaceStore.current().me()]
date: (new Date)
draft: true
+ pristine: true
namespaceId: NamespaceStore.current().id
# If initial JSON was provided, apply it to the new model.
# This is used to apply the values in mailto: links to new drafts
diff --git a/internal_packages/message-list/lib/message-list.cjsx b/internal_packages/message-list/lib/message-list.cjsx
index a603b39a6..7caf05831 100755
--- a/internal_packages/message-list/lib/message-list.cjsx
+++ b/internal_packages/message-list/lib/message-list.cjsx
@@ -173,5 +173,5 @@ MessageList = React.createClass
participants[contact.email] = contact
return _.values(participants)
-MessageList.minWidth = 680
+MessageList.minWidth = 500
MessageList.maxWidth = 900
diff --git a/internal_packages/message-list/stylesheets/message-list.less b/internal_packages/message-list/stylesheets/message-list.less
index edea87ade..161960fa3 100644
--- a/internal_packages/message-list/stylesheets/message-list.less
+++ b/internal_packages/message-list/stylesheets/message-list.less
@@ -44,6 +44,11 @@
}
}
+.mode-split {
+ .message-toolbar-subject {
+ margin-left:@padding-base-horizontal;
+ }
+}
#message-list {
display: flex;
flex-direction: row;
diff --git a/internal_packages/thread-list/lib/draft-list.cjsx b/internal_packages/thread-list/lib/draft-list.cjsx
index f3750acd1..0cb72e51f 100644
--- a/internal_packages/thread-list/lib/draft-list.cjsx
+++ b/internal_packages/thread-list/lib/draft-list.cjsx
@@ -37,7 +37,7 @@ DraftList = React.createClass
columns={@state.columns}
items={@state.items}
selectedId={@state.selectedId}
- onClick={@_onClick}
+ onDoubleClick={@_onDoubleClick}
onSelect={@_onSelect} />
@@ -45,7 +45,7 @@ DraftList = React.createClass
@setState
selectedId: item.id
- _onClick: (item) ->
+ _onDoubleClick: (item) ->
DatabaseStore.localIdForModel(item).then (localId) ->
Actions.composePopoutDraft(localId)
diff --git a/spec-inbox/stores/draft-store-spec.coffee b/spec-inbox/stores/draft-store-spec.coffee
index 62453955b..cf2b4ccdb 100644
--- a/spec-inbox/stores/draft-store-spec.coffee
+++ b/spec-inbox/stores/draft-store-spec.coffee
@@ -5,6 +5,7 @@ NamespaceStore = require '../../src/flux/stores/namespace-store.coffee'
DatabaseStore = require '../../src/flux/stores/database-store.coffee'
DraftStore = require '../../src/flux/stores/draft-store.coffee'
SendDraftTask = require '../../src/flux/tasks/send-draft'
+DestroyDraftTask = require '../../src/flux/tasks/destroy-draft'
Actions = require '../../src/flux/actions'
_ = require 'underscore-plus'
@@ -266,6 +267,84 @@ describe "DraftStore", ->
, (thread, message) ->
expect(message).toEqual(fakeMessage1)
{}
+
+ describe "onDestroyDraft", ->
+ beforeEach ->
+ @draftReset = jasmine.createSpy('draft reset')
+ spyOn(Actions, 'queueTask')
+ DraftStore._draftSessions = {"abc":{
+ draft: ->
+ pristine: false
+ changes:
+ commit: -> Promise.resolve()
+ reset: @draftReset
+ cleanup: ->
+ }}
+
+ it "should reset the draft session, ensuring no more saves are made", ->
+ DraftStore._onDestroyDraft('abc')
+ expect(@draftReset).toHaveBeenCalled()
+
+ it "should not do anything if the draft session is not in the window", ->
+ expect ->
+ DraftStore._onDestroyDraft('other')
+ .not.toThrow()
+ expect(@draftReset).not.toHaveBeenCalled()
+
+ it "should queue a destroy draft task", ->
+ DraftStore._onDestroyDraft('abc')
+ expect(Actions.queueTask).toHaveBeenCalled()
+ expect(Actions.queueTask.mostRecentCall.args[0] instanceof DestroyDraftTask).toBe(true)
+
+ it "should clean up the draft session", ->
+ spyOn(DraftStore, 'cleanupSessionForLocalId')
+ DraftStore._onDestroyDraft('abc')
+ expect(DraftStore.cleanupSessionForLocalId).toHaveBeenCalledWith('abc')
+
+ describe "before unloading", ->
+ it "should destroy pristine drafts", ->
+ DraftStore._draftSessions = {"abc": {
+ changes: {}
+ draft: ->
+ pristine: true
+ }}
+
+ spyOn(Actions, 'queueTask')
+ DraftStore._onBeforeUnload()
+ expect(Actions.queueTask).toHaveBeenCalled()
+ expect(Actions.queueTask.mostRecentCall.args[0] instanceof DestroyDraftTask).toBe(true)
+
+ describe "when drafts return unresolved commit promises", ->
+ beforeEach ->
+ @resolve = null
+ DraftStore._draftSessions = {"abc": {
+ changes:
+ commit: => new Promise (resolve, reject) => @resolve = resolve
+ draft: ->
+ pristine: false
+ }}
+
+ it "should return false and call window.close itself", ->
+ spyOn(window, 'close')
+ expect(DraftStore._onBeforeUnload()).toBe(false)
+ runs ->
+ @resolve()
+ waitsFor ->
+ window.close.callCount > 0
+ runs ->
+ expect(window.close).toHaveBeenCalled()
+
+ describe "when no drafts return unresolved commit promises", ->
+ beforeEach ->
+ DraftStore._draftSessions = {"abc":{
+ changes:
+ commit: -> Promise.resolve()
+ draft: ->
+ pristine: false
+ }}
+
+ it "should return true and allow the window to close", ->
+ expect(DraftStore._onBeforeUnload()).toBe(true)
describe "sending a draft", ->
draftLocalId = "local-123"
@@ -274,13 +353,12 @@ describe "DraftStore", ->
DraftStore._draftSessions = {}
DraftStore._draftSessions[draftLocalId] =
prepare: -> Promise.resolve()
+ cleanup: ->
+ draft: -> {}
changes:
commit: -> Promise.resolve()
spyOn(DraftStore, "trigger")
- afterEach ->
- atom.state.mode = "editor" # reset to default
-
it "sets the sending state when sending", ->
DraftStore._onSendDraft(draftLocalId)
expect(DraftStore.sendingState(draftLocalId)).toBe true
@@ -336,3 +414,57 @@ describe "DraftStore", ->
expect(Actions.queueTask).toHaveBeenCalled()
task = Actions.queueTask.calls[0].args[0]
expect(task.fromPopout).toBe true
+
+ describe "cleanupSessionForLocalId", ->
+ it "should destroy the draft if it is pristine", ->
+ DraftStore._draftSessions = {"abc":{
+ draft: ->
+ pristine: true
+ cleanup: ->
+ }}
+ spyOn(Actions, 'queueTask')
+ DraftStore.cleanupSessionForLocalId('abc')
+ expect(Actions.queueTask).toHaveBeenCalled()
+ expect(Actions.queueTask.mostRecentCall.args[0] instanceof DestroyDraftTask).toBe(true)
+
+ it "should not do anything bad if the session does not exist", ->
+ expect ->
+ DraftStore.cleanupSessionForLocalId('dne')
+ .not.toThrow()
+
+ describe "when in the popout composer", ->
+ beforeEach ->
+ atom.state.mode = 'composer'
+ DraftStore._draftSessions = {"abc":{
+ draft: ->
+ pristine: false
+ cleanup: ->
+ }}
+
+ it "should close the composer window", ->
+ spyOn(atom, 'close')
+ DraftStore.cleanupSessionForLocalId('abc')
+ expect(atom.close).toHaveBeenCalled()
+
+ it "should not close the composer window if the draft session is not in the window", ->
+ spyOn(atom, 'close')
+ DraftStore.cleanupSessionForLocalId('other-random-draft-id')
+ expect(atom.close).not.toHaveBeenCalled()
+
+ describe "when it is in a main window", ->
+ beforeEach ->
+ @cleanup = jasmine.createSpy('cleanup')
+ DraftStore._draftSessions = {"abc":{
+ draft: ->
+ pristine: false
+ cleanup: @cleanup
+ }}
+
+ it "should call proxy.cleanup() to unlink listeners", ->
+ DraftStore.cleanupSessionForLocalId('abc')
+ expect(@cleanup).toHaveBeenCalled()
+
+ it "should remove the proxy from the sessions list", ->
+ DraftStore.cleanupSessionForLocalId('abc')
+ expect(DraftStore._draftSessions).toEqual({})
+
diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee
index 0735dc4a0..78732c2ac 100644
--- a/spec/spec-helper.coffee
+++ b/spec/spec-helper.coffee
@@ -160,7 +160,8 @@ afterEach ->
atom.packages.deactivatePackages()
atom.menu.template = []
atom.contextMenu.clear()
-
+ atom.state.mode = 'spec'
+
atom.themes.removeStylesheet('global-editor-styles')
delete atom.state.packageStates
diff --git a/src/flux/actions.coffee b/src/flux/actions.coffee
index ae0add62f..6b9ecb7fd 100644
--- a/src/flux/actions.coffee
+++ b/src/flux/actions.coffee
@@ -22,9 +22,7 @@ globalActions = [
# Draft actions
"sendDraftError",
- "sendDraftSuccess",
- "destroyDraftSuccess",
- "destroyDraftError"
+ "sendDraftSuccess"
]
# These actions are rebroadcast through the ActionBridge to the
diff --git a/src/flux/models/message.coffee b/src/flux/models/message.coffee
index 6a6139474..fc91cc55d 100644
--- a/src/flux/models/message.coffee
+++ b/src/flux/models/message.coffee
@@ -56,6 +56,11 @@ class Message extends Model
jsonKey: 'draft'
queryable: true
+ 'pristine': Attributes.Boolean
+ modelKey: 'pristine'
+ jsonKey: 'pristine'
+ queryable: false
+
'version': Attributes.Number
modelKey: 'version'
queryable: true
@@ -111,5 +116,4 @@ class Message extends Model
fileIds: ->
_.map @files, (file) -> file.id
-
module.exports = Message
diff --git a/src/flux/stores/analytics-store.coffee b/src/flux/stores/analytics-store.coffee
index bdc70564d..dc4f086eb 100644
--- a/src/flux/stores/analytics-store.coffee
+++ b/src/flux/stores/analytics-store.coffee
@@ -31,8 +31,6 @@ AnalyticsStore = Reflux.createStore
fileUploaded: (uploadData={}) -> {fileSize: uploadData.fileSize}
sendDraftError: (dId, msg) -> {drafLocalId: dId, error: msg}
sendDraftSuccess: (draftLocalId) -> {draftLocalId: draftLocalId}
- destroyDraftSuccess: -> {}
- destroyDraftError: (msg) -> {error: msg}
showDeveloperConsole: -> {}
composeReply: ({threadId, messageId}) -> {threadId, messageId}
composeForward: ({threadId, messageId}) -> {threadId, messageId}
diff --git a/src/flux/stores/draft-store-proxy.coffee b/src/flux/stores/draft-store-proxy.coffee
index f1844b119..938c21ec4 100644
--- a/src/flux/stores/draft-store-proxy.coffee
+++ b/src/flux/stores/draft-store-proxy.coffee
@@ -25,6 +25,7 @@ class DraftChangeSet
add: (changes, immediate) =>
@_pending = _.extend(@_pending, changes)
+ @_pending['pristine'] = false
@_onChange()
if immediate
@commit()
@@ -39,8 +40,8 @@ class DraftChangeSet
DatabaseStore = require './database-store'
DatabaseStore.findByLocalId(Message, @localId).then (draft) =>
draft = @applyToModel(draft)
- @_pending = {}
- DatabaseStore.persistModel(draft)
+ DatabaseStore.persistModel(draft).then =>
+ @_pending = {}
applyToModel: (model) =>
model.fromJSON(@_pending) if model
@@ -93,6 +94,9 @@ class DraftStoreProxy
@_emitter.addListener('trigger', eventHandler)
return =>
@_emitter.removeListener('trigger', eventHandler)
+ if @_emitter.listeners('trigger').length is 0
+ DraftStore = require './draft-store'
+ DraftStore.cleanupSessionForLocalId(@draftLocalId)
cleanup: ->
# Unlink ourselves from the stores/actions we were listening to
diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee
index c5d0fa730..01a221469 100644
--- a/src/flux/stores/draft-store.coffee
+++ b/src/flux/stores/draft-store.coffee
@@ -44,7 +44,6 @@ DraftStore = Reflux.createStore
@listenTo Actions.sendDraftError, @_onSendDraftSuccess
@listenTo Actions.sendDraftSuccess, @_onSendDraftError
- @listenTo Actions.destroyDraftSuccess, @_closeWindow
@_drafts = []
@_draftSessions = {}
@_sendingState = {}
@@ -52,28 +51,7 @@ DraftStore = Reflux.createStore
# TODO: Doesn't work if we do window.addEventListener, but this is
# fragile. Pending an Atom fix perhaps?
- window.onbeforeunload = (event) =>
- promises = []
-
- # Normally we'd just append all promises, even the ones already
- # fulfilled (nothing to save), but in this case we only want to
- # block window closing if we have to do real work. Calling
- # window.close() within on onbeforeunload could do weird things.
- for key, session of @_draftSessions
- promise = session.changes.commit()
- if not promise.isFulfilled()
- promises.push(promise)
-
- if promises.length > 0
- Promise.settle(promises).then =>
- @_draftSessions = {}
- window.close()
-
- # Stop and wait before closing
- return false
- else
- # Continue closing
- return true
+ window.onbeforeunload = => @_onBeforeUnload()
DatabaseStore.findAll(Message, draft: true).then (drafts) =>
@_drafts = drafts
@@ -87,6 +65,7 @@ DraftStore = Reflux.createStore
@_drafts
sessionForLocalId: (localId) ->
+ throw new Error("sessionForLocalId requires a localId") unless localId
@_draftSessions[localId] ?= new DraftStoreProxy(localId)
@_draftSessions[localId]
@@ -104,6 +83,43 @@ DraftStore = Reflux.createStore
@_extensions = _.without(@_extensions, ext)
########### PRIVATE ####################################################
+
+ cleanupSessionForLocalId: (localId) ->
+ return unless @_draftSessions[localId]
+
+ draft = @_draftSessions[localId].draft()
+ Actions.queueTask(new DestroyDraftTask(localId)) if draft.pristine
+
+ if atom.state.mode is "composer"
+ atom.close()
+ else
+ @_draftSessions[localId].cleanup()
+ delete @_draftSessions[localId]
+
+ _onBeforeUnload: ->
+ promises = []
+
+ # Normally we'd just append all promises, even the ones already
+ # fulfilled (nothing to save), but in this case we only want to
+ # block window closing if we have to do real work. Calling
+ # window.close() within on onbeforeunload could do weird things.
+ for key, session of @_draftSessions
+ if session.draft()?.pristine
+ Actions.queueTask(new DestroyDraftTask(session.draftLocalId))
+ else
+ promise = session.changes.commit()
+ promises.push(promise) unless promise.isFulfilled()
+
+ if promises.length > 0
+ Promise.settle(promises).then =>
+ @_draftSessions = {}
+ window.close()
+
+ # Stop and wait before closing
+ return false
+ else
+ # Continue closing
+ return true
_onDataChanged: (change) ->
return unless change.objectClass is Message.name
@@ -199,17 +215,12 @@ DraftStore = Reflux.createStore
from: [NamespaceStore.current().me()]
date: (new Date)
draft: true
+ pristine: true
threadId: thread.id
namespaceId: thread.namespaceId
DatabaseStore.persistModel(draft)
- # We only want to close the popout window if we're sure various draft
- # actions succeeded.
- _closeWindow: (draftLocalId) ->
- if atom.state.mode is "composer" and @_draftSessions[draftLocalId]?
- atom.close()
-
# The logic to create a new Draft used to be in the DraftStore (which is
# where it should be). It got moved to composer/lib/main.cjsx becaues
# of an obscure atom-shell/Chrome bug whereby database requests firing right
@@ -224,13 +235,14 @@ DraftStore = Reflux.createStore
_onDestroyDraft: (draftLocalId) ->
# Immediately reset any pending changes so no saves occur
- @_closeWindow(draftLocalId)
@_draftSessions[draftLocalId]?.changes.reset()
- delete @_draftSessions[draftLocalId]
# Queue the task to destroy the draft
Actions.queueTask(new DestroyDraftTask(draftLocalId))
+ # Clean up the draft session
+ @cleanupSessionForLocalId(draftLocalId)
+
_onSendDraft: (draftLocalId) ->
new Promise (resolve, reject) =>
@_sendingState[draftLocalId] = true
@@ -245,12 +257,13 @@ DraftStore = Reflux.createStore
# Immediately save any pending changes so we don't save after sending
session.changes.commit().then =>
- # We optimistically close the window. If we get an error, then it
- # will re-open again.
- @_closeWindow(draftLocalId)
# Queue the task to send the draft
fromPopout = atom.state.mode is "composer"
Actions.queueTask(new SendDraftTask(draftLocalId, fromPopout: fromPopout))
+
+ # Clean up session, close window
+ @cleanupSessionForLocalId(draftLocalId)
+
resolve()
_onSendDraftError: (draftLocalId) ->
diff --git a/src/flux/stores/thread-store.coffee b/src/flux/stores/thread-store.coffee
index 4c64875d3..cbda7446d 100644
--- a/src/flux/stores/thread-store.coffee
+++ b/src/flux/stores/thread-store.coffee
@@ -103,14 +103,13 @@ ThreadStore = Reflux.createStore
@fetchFromAPI()
_onSelectThreadId: (id) ->
- # Mark the *previously* selected thread as read,
- # before we bring in the next thread
+ return if @_selectedId == id
+ @_selectedId = id
+
thread = @selectedThread()
if thread && thread.isUnread()
thread.markAsRead()
- return if @_selectedId == id
- @_selectedId = id
@trigger()
# Accessing Data
diff --git a/src/sheet-container.cjsx b/src/sheet-container.cjsx
index 5cae071f9..729860449 100644
--- a/src/sheet-container.cjsx
+++ b/src/sheet-container.cjsx
@@ -91,7 +91,7 @@ Toolbar = React.createClass
{@_flexboxForItems(items)}
-
+
{toolbars}
diff --git a/src/sheet.cjsx b/src/sheet.cjsx
index 9ff5ec8c5..f846f8d2d 100644
--- a/src/sheet.cjsx
+++ b/src/sheet.cjsx
@@ -51,6 +51,7 @@ Sheet = React.createClass
{@_columnFlexboxElements()}
@@ -84,7 +85,7 @@ Sheet = React.createClass
_getStateFromStores: ->
- state =
+ state =
mode: WorkspaceStore.selectedLayoutMode()
columns: []
diff --git a/static/buttons.less b/static/buttons.less
index 47cc26d6d..561c9d9aa 100644
--- a/static/buttons.less
+++ b/static/buttons.less
@@ -46,14 +46,26 @@ button, html input[type="button"] {
color: @btn-default-text-color;
background: @btn-default-bg-color;
+
&.btn-action {
color: @btn-action-text-color;
background: @btn-action-bg-color;
}
+
&.btn-emphasis {
- color: @btn-emphasis-text-color;
- background: @btn-emphasis-bg-color;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(lighten(@btn-emphasis-bg-color,10%)), to(@btn-emphasis-bg-color));
+ border:1px solid darken(@btn-emphasis-bg-color, 5%);
+ color: @btn-emphasis-text-color;
+ font-weight: @font-weight-medium;
+
+ img {-webkit-filter: brightness(100);}
}
+
+ &.btn-emphasis:active {
+ background-image: -webkit-gradient(linear, left top, left bottom, from(darken(@btn-emphasis-bg-color,10%)), to(darken(@btn-emphasis-bg-color, 4%)));
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.21);
+ }
+
&.btn-danger, .btn-destructive {
color: @btn-danger-text-color;
background: @btn-danger-bg-color;
diff --git a/static/images/toolbar/toolbar-send@2x.png b/static/images/toolbar/toolbar-send@2x.png
index 038270826..9d04450b6 100644
Binary files a/static/images/toolbar/toolbar-send@2x.png and b/static/images/toolbar/toolbar-send@2x.png differ
diff --git a/static/variables/ui-variables.less b/static/variables/ui-variables.less
index 8abf6adf1..ac1bec180 100644
--- a/static/variables/ui-variables.less
+++ b/static/variables/ui-variables.less
@@ -236,7 +236,7 @@
@btn-action-bg-color: @success-color;
@btn-action-text-color: @text-color;
-@btn-emphasis-bg-color: @accent-primary;
+@btn-emphasis-bg-color: #5b90fb;
@btn-emphasis-text-color: @text-color-inverse;
@btn-danger-bg-color: @danger-color;
@@ -429,7 +429,6 @@
@teal: @PANTONE-326-UP;
@black: @PANTONE-Process-Black-UP;
@cool-gray: @PANTONE-Cool-Gray-1-UP;
-@white: #f1f1f1;
@blue-grey: @blue-gray;
@light-grey: @light-gray;