diff --git a/internal_packages/account-sidebar/stylesheets/account-sidebar.less b/internal_packages/account-sidebar/stylesheets/account-sidebar.less index b818d507f..92179135f 100644 --- a/internal_packages/account-sidebar/stylesheets/account-sidebar.less +++ b/internal_packages/account-sidebar/stylesheets/account-sidebar.less @@ -4,7 +4,7 @@ #account-sidebar { order: 2; height: 100%; - overflow: auto; + overflow-y: auto; background-color: @source-list-bg; @@ -44,11 +44,11 @@ &.selected { background: @source-list-active-bg; color: @source-list-active-color; - img.colorfill { + img.colorfill { background: @source-list-active-color; } } - + &:hover { background: darken(@source-list-bg, 5%); cursor: default; diff --git a/internal_packages/composer/lib/main.cjsx b/internal_packages/composer/lib/main.cjsx index e70e41453..e76769222 100644 --- a/internal_packages/composer/lib/main.cjsx +++ b/internal_packages/composer/lib/main.cjsx @@ -10,10 +10,8 @@ NewComposeButton = require('./new-compose-button') ComposerView = require('./composer-view') module.exports = - item: null # The DOM item the main React component renders into activate: (@state={}) -> - atom.registerHotWindow windowType: "composer" replenishNum: 2 @@ -26,66 +24,33 @@ module.exports = @_activateComposeButton() else @_setupContainer() - windowProps = atom.getLoadSettings().windowProps ? {} - @windowPropsChanged(windowProps) - windowPropsChanged: (newWindowProps) -> - @_prepareDraft(newWindowProps).then (draftLocalId) => - React.render( - , @item - ) - if newWindowProps.errorMessage - @_showInitialErrorDialog(newWindowProps.errorMessage) - .catch (error) -> - console.error(error.stack) + windowPropsReceived: ({draftLocalId, errorMessage}) -> + return unless @_container + React.render( + , @_container + ) + if errorMessage + @_showInitialErrorDialog(errorMessage) deactivate: -> if atom.isMainWindow() - React.unmountComponentAtNode(@new_compose_button) - @new_compose_button.remove() - @new_compose_button = null + React.unmountComponentAtNode(@_composeButton) + @_composeButton.remove() + @_composeButton = null else - React.unmountComponentAtNode(@item) - @item.remove() - @item = null + React.unmountComponentAtNode(@_container) + @_container.remove() + @_container = null serialize: -> @state _setupContainer: -> - if @item? then return # Activate once - @item = document.createElement("div") - @item.setAttribute("id", "composer-full-window") - @item.setAttribute("class", "composer-full-window") - document.body.appendChild(@item) - - # This logic used to be in the DraftStore (which is where it should be). It - # got moved here becaues of an obscure atom-shell/Chrome bug whereby database - # requests firing right before the new-window loaded would cause the - # new-window to load with about:blank instead of its contents. By moving the - # DB logic here, we can get around this. - _prepareDraft: ({draftLocalId, draftInitialJSON}={}) -> - # The NamespaceStore isn't set yet in the new window, populate it first. - NamespaceStore.populateItems().then -> - new Promise (resolve, reject) -> - if draftLocalId? - resolve(draftLocalId) - else - # Create a new draft - draft = new Message - body: "" - 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 - if draftInitialJSON - draft.fromJSON(draftInitialJSON) - - DatabaseStore.persistModel(draft).then -> - DatabaseStore.localIdForModel(draft).then(resolve).catch(reject) - .catch(reject) + if @_container? then return # Activate once + @_container = document.createElement("div") + @_container.setAttribute("id", "composer-full-window") + @_container.setAttribute("class", "composer-full-window") + document.body.appendChild(@_container) _activateComposeButton: -> ComponentRegistry.register NewComposeButton, diff --git a/internal_packages/inbox-activity-bar/stylesheets/activity-bar.less b/internal_packages/inbox-activity-bar/stylesheets/activity-bar.less index dbb3f2d7e..aa12e7036 100755 --- a/internal_packages/inbox-activity-bar/stylesheets/activity-bar.less +++ b/internal_packages/inbox-activity-bar/stylesheets/activity-bar.less @@ -121,6 +121,10 @@ padding-bottom:3px; } .item.status-code-500, + .item.status-code-501, + .item.status-code-502, + .item.status-code-503, + .item.status-code-504, .item.status-code-400, .item.status-code-404, .item.status-code-409 { diff --git a/internal_packages/message-list/lib/message-list.cjsx b/internal_packages/message-list/lib/message-list.cjsx index ae0d60c9b..578fe43f6 100755 --- a/internal_packages/message-list/lib/message-list.cjsx +++ b/internal_packages/message-list/lib/message-list.cjsx @@ -12,6 +12,9 @@ MessageItem = require "./message-item" class MessageList extends React.Component @displayName: 'MessageList' @containerRequired: false + @containerStyles: + minWidth: 500 + maxWidth: 900 constructor: (@props) -> @state = @_getStateFromStores() @@ -253,7 +256,5 @@ class MessageList extends React.Component _wasAtBottom: => (@_lastScrollTop + @_lastHeight) >= @_lastScrollHeight -MessageList.minWidth = 500 -MessageList.maxWidth = 900 module.exports = MessageList diff --git a/internal_packages/thread-list/lib/draft-list.cjsx b/internal_packages/thread-list/lib/draft-list.cjsx index 31fcc457a..2ff531b5b 100644 --- a/internal_packages/thread-list/lib/draft-list.cjsx +++ b/internal_packages/thread-list/lib/draft-list.cjsx @@ -69,11 +69,10 @@ class DraftList extends React.Component # Additional Commands _onDelete: ({focusedId}) => - item = @state.dataView.getById(focusedId) + item = DraftListStore.view().getById(focusedId) return unless item DatabaseStore.localIdForModel(item).then (localId) -> Actions.destroyDraft(localId) - @_onShiftSelectedIndex(-1) module.exports = DraftList diff --git a/src/atom.coffee b/src/atom.coffee index 83c0a3176..52a689e52 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -84,10 +84,8 @@ class Atom extends Model # Returns the load settings hash associated with the current window. @getLoadSettings: -> - # We pull from the window object instead of the url so the - # loadSettings can change post bootup. This is very useful for - # starting hot windows. - @loadSettings ?= @getCurrentWindow().loadSettings + @loadSettings ?= JSON.parse(decodeURIComponent(location.search.substr(14))) + cloned = _.deepClone(@loadSettings) # The loadSettings.windowState could be large, request it only when needed. cloned.__defineGetter__ 'windowState', => @@ -228,8 +226,6 @@ class Atom extends Model @subscribe @packages.onDidActivateInitialPackages => @watchThemes() @windowEventHandler = new WindowEventHandler - ipc.on("window-props-changed", => @windowPropsChanged()) - # Start our error reporting to the backend and attach error handlers # to the window and the Bluebird Promise library, converting things # back through the sourcemap as necessary. @@ -452,19 +448,16 @@ class Atom extends Model reload: -> ipc.send('call-window-method', 'restart') - # Calls the `windowPropsChanged` method of all packages that are + # Calls the `windowPropsReceived` method of all packages that are # currently loaded - windowPropsChanged: -> - # This will cause it to get refreshed the next time they're queried - @constructor.loadSettings = null + loadSettingsChanged: (loadSettings) => + @loadSettings = loadSettings + {width, height, windowProps} = loadSettings - {width, - height, - windowProps} = @getLoadSettings() + @packages.windowPropsReceived(windowProps ? {}) - @packages.windowPropsChanged(windowProps) - - @setWindowDimensions({width, height}) if width and height + if width and height + @setWindowDimensions({width, height}) # Extended: Returns a {Boolean} true when the current window is maximized. isMaximixed: -> @@ -620,6 +613,7 @@ class Atom extends Model {width, height, windowType, + windowProps, windowPackages} = @getLoadSettings() @loadConfig() @@ -633,6 +627,9 @@ class Atom extends Model @packages.loadPackage(pack) for pack in (windowPackages ? []) @packages.activate() + ipc.on("load-settings-changed", @loadSettingsChanged) + @packages.windowPropsReceived(windowProps ? {}) + @keymaps.loadUserKeymap() @setWindowDimensions({width, height}) if width and height @@ -645,6 +642,7 @@ class Atom extends Model logout: -> if @isLoggedIn() @config.set('inbox', null) + @config.set('edgehill', null) Actions = require './flux/actions' Actions.logout() @hide() diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index 87a081de6..f00471c70 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -20,6 +20,8 @@ class AtomWindow constructor: (settings={}) -> {frame, title, + width, + height, resizable, pathToOpen, hideMenuBar, @@ -38,6 +40,8 @@ class AtomWindow show: false title: title ? 'Nilas' frame: frame ? true + width: width + height: height resizable: resizable ? true icon: @constructor.iconPath 'auto-hide-menu-bar': hideMenuBar @@ -78,6 +82,8 @@ class AtomWindow @browserWindow.once 'window:loaded', => @emit 'window:loaded' @loaded = true + if @browserWindow.loadSettingsChangedSinceGetURL + @browserWindow.webContents.send('load-settings-changed', @browserWindow.loadSettings) @browserWindow.loadUrl @getUrl(loadSettings) @browserWindow.focusOnWebView() if @isSpec @@ -88,6 +94,8 @@ class AtomWindow setLoadSettings: (loadSettings) -> @browserWindow.loadSettings = loadSettings + @browserWindow.loadSettingsChangedSinceGetURL = true + @browserWindow.webContents.send('load-settings-changed', loadSettings) if @loaded getUrl: (loadSettingsObj) -> # Ignore the windowState when passing loadSettings via URL, since it could @@ -95,6 +103,8 @@ class AtomWindow loadSettings = _.clone(loadSettingsObj) delete loadSettings['windowState'] + @browserWindow.loadSettingsChangedSinceGetURL = false + url.format protocol: 'file' pathname: "#{@resourcePath}/static/index.html" @@ -212,6 +222,15 @@ class AtomWindow show: -> @browserWindow.show() + showWhenLoaded: -> + if @loaded + @show() + @focus() + else + @once 'window:loaded', => + @show() + @focus() + focus: -> @browserWindow.focus() minimize: -> @browserWindow.minimize() diff --git a/src/browser/edgehill-application.coffee b/src/browser/edgehill-application.coffee index e94ce4eff..0fa5f29bd 100644 --- a/src/browser/edgehill-application.coffee +++ b/src/browser/edgehill-application.coffee @@ -11,7 +11,6 @@ path = require 'path' os = require 'os' net = require 'net' url = require 'url' -qs = require 'querystring' exec = require('child_process').exec querystring = require 'querystring' {EventEmitter} = require 'events' @@ -121,7 +120,7 @@ class AtomApplication # # This means that when `newWindow` is called, instead of going through # the bootup process, it simply replaces key parameters and does a soft - # reload via `windowPropsChanged`. + # reload via `windowPropsReceived`. # # Since the window is already loaded, there are only some options that # can be soft-reloaded. If you attempt to pass options that a soft @@ -213,9 +212,8 @@ class AtomApplication newColdWindow: (options={}) -> options = _.extend(@defaultWindowOptions(), options) - w = new AtomWindow options - w.show() - w.focus() + w = new AtomWindow(options) + w.showWhenLoaded() # Tries to create a new hot window. Since we're updating an existing # window instead of creatinga new one, there are limitations in the @@ -232,19 +230,10 @@ class AtomApplication options.windowPackages = hotWindowParams.windowPackages @newColdWindow(options) else - win = hotWindowParams.loadedWindows.pop() + [win] = hotWindowParams.loadedWindows.splice(0,1) newLoadSettings = _.extend(win.loadSettings(), options) - - # This will update the internal instance variable that the window will - # query for its load settings. win.setLoadSettings(newLoadSettings) - - # This is expected to be caught by the main application to re-fetch - # the loadSettings and re-render itself accordingly. - win.browserWindow.webContents.send('window-props-changed') - - win.show() - win.focus() + win.showWhenLoaded() @_replenishHotWindows() @@ -283,6 +272,7 @@ class AtomApplication @_replenishQueue.push(optionsArray.shift()) @_processReplenishQueue() + _replenishHotWindows: _.debounce(AtomApplication::__replenishHotWindows, 100) _processReplenishQueue: -> @@ -619,19 +609,7 @@ class AtomApplication # Attempt to parse the mailto link into Message object JSON # and then open a composer window if parts.protocol is 'mailto:' - query = qs.parse(parts.query) - query.to = "#{parts.auth}@#{parts.host}" - - json = { - subject: query.subject || '', - body: query.body || '', - } - - emailToObj = (email) -> {email: email, object: 'Contact'} - for attr in ['to', 'cc', 'bcc'] - json[attr] = query[attr]?.split(',').map(emailToObj) || [] - - @mainWindow.browserWindow.webContents.send('mailto', json) + @mainWindow.browserWindow.webContents.send('mailto', urlToOpen) # The host of the URL being opened is assumed to be the package name # responsible for opening the URL. A new window will be created with diff --git a/src/flux/models/contact.coffee b/src/flux/models/contact.coffee index 78f4f17b9..82bdb3ade 100644 --- a/src/flux/models/contact.coffee +++ b/src/flux/models/contact.coffee @@ -45,15 +45,15 @@ class Contact extends Model # - `name` if the contact has a populated name value # - `email` in all other cases. displayName: -> - return "You" if @email == NamespaceStore.current().emailAddress + return "You" if @email is NamespaceStore.current()?.emailAddress @_nameParts().join(' ') displayFirstName: -> - return "You" if @email == NamespaceStore.current().emailAddress + return "You" if @email is NamespaceStore.current()?.emailAddress @firstName() displayLastName: -> - return "" if @email == NamespaceStore.current().emailAddress + return "" if @email is NamespaceStore.current()?.emailAddress @lastName() firstName: -> diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee index 015105728..ba7e70f3b 100644 --- a/src/flux/stores/draft-store.coffee +++ b/src/flux/stores/draft-store.coffee @@ -5,11 +5,13 @@ ipc = require 'ipc' DraftStoreProxy = require './draft-store-proxy' DatabaseStore = require './database-store' NamespaceStore = require './namespace-store' +ContactStore = require './contact-store' SendDraftTask = require '../tasks/send-draft' DestroyDraftTask = require '../tasks/destroy-draft' Thread = require '../models/thread' +Contact = require '../models/contact' Message = require '../models/message' MessageUtils = require '../models/message-utils' Actions = require '../actions' @@ -40,11 +42,11 @@ class DraftStore @listenTo Actions.composeReply, @_onComposeReply @listenTo Actions.composeForward, @_onComposeForward @listenTo Actions.composeReplyAll, @_onComposeReplyAll - @listenTo Actions.composePopoutDraft, @_onComposePopoutDraft - @listenTo Actions.composeNewBlankDraft, @_onComposeNewBlankDraft + @listenTo Actions.composePopoutDraft, @_onPopoutDraftLocalId + @listenTo Actions.composeNewBlankDraft, @_onPopoutBlankDraft atom.commands.add 'body', - 'application:new-message': => @_onComposeNewBlankDraft() + 'application:new-message': => @_onPopoutBlankDraft() @listenTo Actions.sendDraft, @_onSendDraft @listenTo Actions.destroyDraft, @_onDestroyDraft @@ -59,9 +61,7 @@ class DraftStore @_sendingState = {} @_extensions = [] - ipc.on 'mailto', (mailToJSON) => - return unless atom.isMainWindow() - atom.newWindow @_composerWindowProps(draftInitialJSON: mailToJSON) + ipc.on 'mailto', @_onHandleMailtoLink # TODO: Doesn't work if we do window.addEventListener, but this is # fragile. Pending an Atom fix perhaps? @@ -301,22 +301,54 @@ class DraftStore re = new RegExp("", "igm") body.replace(re, "") - # 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 - # before the new-window loaded would cause the new-window to load with - # about:blank instead of its contents. By moving the DB logic there, we can - # get around this. - _onComposeNewBlankDraft: => - atom.newWindow @_composerWindowProps() + _onPopoutBlankDraft: => + draft = new Message + body: "" + from: [NamespaceStore.current().me()] + date: (new Date) + draft: true + pristine: true + namespaceId: NamespaceStore.current().id + DatabaseStore.persistModel(draft).then => + DatabaseStore.localIdForModel(draft).then(@_onPopoutDraftLocalId) - _onComposePopoutDraft: (draftLocalId) => - atom.newWindow @_composerWindowProps(draftLocalId: draftLocalId) + _onPopoutDraftLocalId: (draftLocalId, options = {}) => + options.draftLocalId = draftLocalId - _composerWindowProps: (props={}) => - title: "Message" - windowType: "composer" - windowProps: _.extend {}, props + atom.newWindow + title: "Message" + windowType: "composer" + windowProps: options + + _onHandleMailtoLink: (urlString) => + namespace = NamespaceStore.current() + return unless namespace + + url = require 'url' + qs = require 'querystring' + parts = url.parse(urlString) + query = qs.parse(parts.query) + query.to = "#{parts.auth}@#{parts.host}" + + draft = new Message + body: query.body || '' + subject: query.subject || '', + from: [namespace.me()] + date: (new Date) + draft: true + pristine: true + namespaceId: namespace.id + + contactForEmail = (email) -> + match = ContactStore.searchContacts(email, 1) + return match[0] if match[0] + return new Contact({email}) + + for attr in ['to', 'cc', 'bcc'] + draft[attr] = query[attr]?.split(',').map(contactForEmail) || [] + + DatabaseStore.persistModel(draft).then => + DatabaseStore.localIdForModel(draft).then(@_onPopoutDraftLocalId) _onDestroyDraft: (draftLocalId) => # Immediately reset any pending changes so no saves occur @@ -354,7 +386,7 @@ class DraftStore _onSendDraftError: (draftLocalId, errorMessage) -> @_sendingState[draftLocalId] = false if atom.getWindowType() is "composer" - atom.newWindow @_composerWindowProps({errorMessage, draftLocalId}) + @_onPopoutDraftLocalId(draftLocalId, {errorMessage}) @trigger() _onSendDraftSuccess: (draftLocalId) => diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 7dab4c599..5905952bf 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -445,5 +445,5 @@ class PackageManager delete @activePackages[pack.name] @emitter.emit 'did-deactivate-package', pack - windowPropsChanged: (windowProps) -> - pack.windowPropsChanged?(windowProps) for pack in @getLoadedPackages() + windowPropsReceived: (windowProps) -> + pack.windowPropsReceived?(windowProps) for pack in @getLoadedPackages() diff --git a/src/package.coffee b/src/package.coffee index f39c4cad8..c5d07f37e 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -154,8 +154,8 @@ class Package Q.all([@grammarsPromise, @settingsPromise, @activationDeferred.promise]) - windowPropsChanged: (windowProps) -> - @mainModule?.windowPropsChanged?(windowProps) + windowPropsReceived: (windowProps) -> + @mainModule?.windowPropsReceived?(windowProps) activateNow: -> try