diff --git a/internal_packages/composer/lib/composer-view.cjsx b/internal_packages/composer/lib/composer-view.cjsx index 1d7425cc2..61e1c296d 100644 --- a/internal_packages/composer/lib/composer-view.cjsx +++ b/internal_packages/composer/lib/composer-view.cjsx @@ -159,44 +159,7 @@ class ComposerView extends React.Component - - - @setState showcc: false} - participants={to: @state['to'], cc: @state['cc'], bcc: @state['bcc']} - tabIndex='103'/> - - @setState showbcc: false} - participants={to: @state['to'], cc: @state['cc'], bcc: @state['bcc']} - tabIndex='104'/> - -
- -
+ {@_renderFields()}
+ _renderFields: => + # Note: We need to physically add and remove these elements, not just hide them. + # If they're hidden, shift-tab between fields breaks. + fields = [] + fields.push( + + ) + + if @state.showcc + fields.push( + @setState showcc: false} + participants={to: @state['to'], cc: @state['cc'], bcc: @state['bcc']} + tabIndex='103'/> + ) + + if @state.showbcc + fields.push( + @setState showbcc: false} + participants={to: @state['to'], cc: @state['cc'], bcc: @state['bcc']} + tabIndex='104'/> + ) + + if @state.showsubject + fields.push( +
+ +
+ ) + + fields + _renderFooterRegions: => return
unless @props.localId diff --git a/internal_packages/composer/lib/contenteditable-component.cjsx b/internal_packages/composer/lib/contenteditable-component.cjsx index 210021af4..a968cbc86 100644 --- a/internal_packages/composer/lib/contenteditable-component.cjsx +++ b/internal_packages/composer/lib/contenteditable-component.cjsx @@ -43,12 +43,13 @@ class ContenteditableComponent extends React.Component range = @_getRangeInScope() for extension in DraftStore.extensions() extension.onFocusNext(editableNode, range, event) if extension.onFocusNext + 'core:focus-previous': (event) => editableNode = @_editableNode() range = @_getRangeInScope() for extension in DraftStore.extensions() extension.onFocusPrevious(editableNode, range, event) if extension.onFocusPrevious - } + } @_cleanHTML() diff --git a/internal_packages/composer/lib/main.cjsx b/internal_packages/composer/lib/main.cjsx index 4fbaec80f..4a51467ca 100644 --- a/internal_packages/composer/lib/main.cjsx +++ b/internal_packages/composer/lib/main.cjsx @@ -55,6 +55,7 @@ module.exports = ComponentRegistry.register ComposeButton, location: WorkspaceStore.Location.RootSidebar.Toolbar else + atom.getCurrentWindow().setMinimumSize(600, 400) WorkspaceStore.defineSheet 'Main', {root: true}, list: ['Center'] ComponentRegistry.register ComposerWithWindowProps, diff --git a/internal_packages/composer/lib/participants-text-field.cjsx b/internal_packages/composer/lib/participants-text-field.cjsx index 7f68c045b..8f4059b40 100644 --- a/internal_packages/composer/lib/participants-text-field.cjsx +++ b/internal_packages/composer/lib/participants-text-field.cjsx @@ -17,9 +17,6 @@ class ParticipantsTextField extends React.Component # to modify the `participants` provided. field: React.PropTypes.string, - # Whether or not the field should be visible. Defaults to true. - visible: React.PropTypes.bool - # An object containing arrays of participants. Typically, this is # {to: [], cc: [], bcc: []}. Each ParticipantsTextField needs all of # the values, because adding an element to one field may remove it @@ -36,7 +33,7 @@ class ParticipantsTextField extends React.Component render: => classSet = {} classSet[@props.field] = true -
+
describe "when focus() is called", -> describe "if a field name is provided", -> it "should focus that field", -> - useDraft.call(@) + useDraft.call(@, cc: [u2]) makeComposer.call(@) spyOn(@composer.refs['textFieldCc'], 'focus') @composer.focus('textFieldCc') diff --git a/internal_packages/composer/stylesheets/composer.less b/internal_packages/composer/stylesheets/composer.less index 25ae2924c..475729dd4 100644 --- a/internal_packages/composer/stylesheets/composer.less +++ b/internal_packages/composer/stylesheets/composer.less @@ -28,15 +28,15 @@ width: 100%; background: transparent; border-bottom: 0; - max-width: @compose-width; - margin: 0 auto; .composer-action-bar-content { display:flex; + margin: 0 auto; flex-direction:row; - margin-left: -@spacing-standard / 2; - margin-right: -@spacing-standard / 2; - padding: @spacing-standard @spacing-double; + max-width: @compose-width; + padding-left: @spacing-standard + @spacing-standard / 2; + padding-right: @spacing-standard + @spacing-standard / 2; + padding-top: @spacing-standard; padding-bottom: @spacing-standard*1.1; > * { @@ -109,7 +109,7 @@ margin: 0 @spacing-standard; border-bottom: 1px solid @border-color-divider; flex-shrink:0; - + .subject-label { color: @text-color-very-subtle; float: left; diff --git a/internal_packages/message-list/lib/message-timestamp.cjsx b/internal_packages/message-list/lib/message-timestamp.cjsx index f1655a4e3..1f0975506 100644 --- a/internal_packages/message-list/lib/message-timestamp.cjsx +++ b/internal_packages/message-list/lib/message-timestamp.cjsx @@ -23,7 +23,7 @@ class MessageTimestamp extends React.Component _timeFormat: => if @props.isDetailed - return "DD / MM / YYYY h:mm a z" + return "MMMM D, YYYY [at] h:mm A" else today = moment(@_today()) dayOfEra = today.dayOfYear() + today.year() * 365 diff --git a/internal_packages/message-list/spec/message-timestamp-spec.cjsx b/internal_packages/message-list/spec/message-timestamp-spec.cjsx index c0f1947e2..322921fc8 100644 --- a/internal_packages/message-list/spec/message-timestamp-spec.cjsx +++ b/internal_packages/message-list/spec/message-timestamp-spec.cjsx @@ -38,4 +38,4 @@ describe "MessageTimestamp", -> ) spyOn(itemDetailed, "_today").andCallFake -> testDate() - expect(itemDetailed._timeFormat()).toBe "DD / MM / YYYY h:mm a z" + expect(itemDetailed._timeFormat()).toBe "MMMM D, YYYY [at] h:mm A" diff --git a/internal_packages/message-templates/lib/template-picker.cjsx b/internal_packages/message-templates/lib/template-picker.cjsx index 4907cb9b4..80b02c53a 100644 --- a/internal_packages/message-templates/lib/template-picker.cjsx +++ b/internal_packages/message-templates/lib/template-picker.cjsx @@ -37,7 +37,7 @@ class TemplatePicker extends React.Component ] footerComponents = [ -
Save as Template...
+
Save Draft as Template...
Open Templates Folder...
] diff --git a/internal_packages/message-templates/lib/template-store.coffee b/internal_packages/message-templates/lib/template-store.coffee index 06b671ad4..886abd9e7 100644 --- a/internal_packages/message-templates/lib/template-store.coffee +++ b/internal_packages/message-templates/lib/template-store.coffee @@ -1,6 +1,7 @@ Reflux = require 'reflux' _ = require 'underscore' {DatabaseStore, DraftStore, Actions, Message} = require 'nylas-exports' +shell = require 'shell' path = require 'path' fs = require 'fs-plus' @@ -59,21 +60,32 @@ TemplateStore = Reflux.createStore draft = session.draft() name ?= draft.subject contents ?= draft.body + if not name or name.length is 0 + return @_displayError("Give your draft a subject to name your template.") + if not contents or contents.length is 0 + return @_displayError("To create a template you need to fill the body of the current draft.") @_writeTemplate(name, contents) + else + if not name or name.length is 0 + return @_displayError("You must provide a name for your template.") + if not contents or contents.length is 0 + return @_displayError("You must provide contents for your template.") @_writeTemplate(name, contents) _onShowTemplates: -> - # show in finder how? - shell = require 'shell' shell.showItemInFolder(@_items[0]?.path || @_templatesDir) + _displayError: (message) -> + dialog = require('remote').require('dialog') + dialog.showErrorBox('Template Creation Error', message) + _writeTemplate: (name, contents) -> - throw new Error("You must provide a template name") unless name - throw new Error("You must provide template contents") unless contents filename = "#{name}.html" templatePath = path.join(@_templatesDir, filename) fs.writeFile templatePath, contents, (err) => + @_displayError(err) if err + shell.showItemInFolder(templatePath) @_items.push id: filename, name: name, diff --git a/internal_packages/message-templates/spec/template-store-spec.coffee b/internal_packages/message-templates/spec/template-store-spec.coffee index 344111e69..f35a75ed3 100644 --- a/internal_packages/message-templates/spec/template-store-spec.coffee +++ b/internal_packages/message-templates/spec/template-store-spec.coffee @@ -18,6 +18,9 @@ stubTemplates = [ describe "TemplateStore", -> beforeEach -> spyOn(fs, 'mkdir') + spyOn(shell, 'showItemInFolder').andCallFake -> + spyOn(fs, 'writeFile').andCallFake (path, contents, callback) -> + callback(null) spyOn(fs, 'readFile').andCallFake (path, callback) -> filename = path.split('/').pop() callback(null, stubTemplateFiles[filename]) @@ -71,9 +74,15 @@ describe "TemplateStore", -> describe "onCreateTemplate", -> beforeEach -> - spyOn(fs, 'readdir').andCallFake (path, callback) -> callback(null, []) - spyOn(fs, 'writeFile').andCallFake (path, contents, callback) -> callback(null) TemplateStore.init() + spyOn(DraftStore, 'sessionForLocalId').andCallFake (draftLocalId) -> + if draftLocalId is 'localid-nosubject' + d = new Message(subject: '', body: '

Body

') + else + d = new Message(subject: 'Subject', body: '

Body

') + session = + draft: -> d + Promise.resolve(session) it "should create a template with the given name and contents", -> TemplateStore._onCreateTemplate({name: '123', contents: 'bla'}) @@ -82,11 +91,15 @@ describe "TemplateStore", -> expect(item.name).toBe "123" expect(item.path.split("/").pop()).toBe "123.html" - it "should throw an exception if no name is provided", -> - expect( -> TemplateStore._onCreateTemplate({contents: 'bla'})).toThrow() + it "should display an error if no name is provided", -> + spyOn(TemplateStore, '_displayError') + TemplateStore._onCreateTemplate({contents: 'bla'}) + expect(TemplateStore._displayError).toHaveBeenCalled() - it "should throw an exception if no content is provided", -> - expect( -> TemplateStore._onCreateTemplate({name: 'bla'})).toThrow() + it "should display an error if no content is provided", -> + spyOn(TemplateStore, '_displayError') + TemplateStore._onCreateTemplate({name: 'bla'}) + expect(TemplateStore._displayError).toHaveBeenCalled() it "should save the template file to the templates folder", -> TemplateStore._onCreateTemplate({name: '123', contents: 'bla'}) @@ -95,17 +108,30 @@ describe "TemplateStore", -> expect(fs.writeFile.mostRecentCall.args[0]).toEqual(path) expect(fs.writeFile.mostRecentCall.args[1]).toEqual('bla') + it "should open the template so you can see it", -> + TemplateStore._onCreateTemplate({name: '123', contents: 'bla'}) + path = "#{stubTemplatesDir}/123.html" + expect(shell.showItemInFolder).toHaveBeenCalled() + describe "when given a draft id", -> it "should create a template from the name and contents of the given draft", -> - draft = new Message - subject: 'Subject' - body: '

Body

' - spyOn(DatabaseStore, 'findByLocalId').andReturn(Promise.resolve(draft)) - TemplateStore._onCreateTemplate({draftLocalId: 'localid-b'}) - expect(TemplateStore.items()).toEqual([]) + runs -> + TemplateStore._onCreateTemplate({draftLocalId: 'localid-b'}) + waitsFor -> + fs.writeFile.callCount > 0 + runs -> + expect(TemplateStore.items().length).toEqual(1) + + it "should display an error if the draft has no subject", -> + spyOn(TemplateStore, '_displayError') + runs -> + TemplateStore._onCreateTemplate({draftLocalId: 'localid-nosubject'}) + waitsFor -> + TemplateStore._displayError.callCount > 0 + runs -> + expect(TemplateStore._displayError).toHaveBeenCalled() describe "onShowTemplates", -> it "should open the templates folder in the Finder", -> - spyOn(shell, 'showItemInFolder') TemplateStore._onShowTemplates() expect(shell.showItemInFolder).toHaveBeenCalled() diff --git a/internal_packages/unread-badge/lib/unread-badge-store.coffee b/internal_packages/unread-badge/lib/unread-badge-store.coffee index 60ee23253..1b5c0a3a6 100644 --- a/internal_packages/unread-badge/lib/unread-badge-store.coffee +++ b/internal_packages/unread-badge/lib/unread-badge-store.coffee @@ -36,7 +36,7 @@ AppUnreadBadgeStore = Reflux.createStore AppUnreadCount = count if count > 999 - app.dock?.setBadge?("\u221E") + app.dock?.setBadge?("999+") else if count > 0 app.dock?.setBadge?("#{count}") else