mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-11 18:32:20 +08:00
1e8fd46342
Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
342 lines
12 KiB
CoffeeScript
342 lines
12 KiB
CoffeeScript
ipc = require 'ipc'
|
|
path = require 'path'
|
|
Q = require 'q'
|
|
_ = require 'underscore-plus'
|
|
Delegator = require 'delegato'
|
|
{deprecate, logDeprecationWarnings} = require 'grim'
|
|
scrollbarStyle = require 'scrollbar-style'
|
|
{$, $$, View} = require './space-pen-extensions'
|
|
fs = require 'fs-plus'
|
|
Workspace = require './workspace'
|
|
PaneView = require './pane-view'
|
|
PaneContainerView = require './pane-container-view'
|
|
TextEditor = require './text-editor'
|
|
|
|
# Deprecated: The top-level view for the entire window. An instance of this class is
|
|
# available via the `atom.workspaceView` global.
|
|
#
|
|
# It is backed by a model object, an instance of {Workspace}, which is available
|
|
# via the `atom.workspace` global or {::getModel}. You should prefer to interact
|
|
# with the model object when possible, but it won't always be possible with the
|
|
# current API.
|
|
#
|
|
# ## Adding Perimeter Panels
|
|
#
|
|
# Use the following methods if possible to attach panels to the perimeter of the
|
|
# workspace rather than manipulating the DOM directly to better insulate you to
|
|
# changes in the workspace markup:
|
|
#
|
|
# * {::prependToTop}
|
|
# * {::appendToTop}
|
|
# * {::prependToBottom}
|
|
# * {::appendToBottom}
|
|
# * {::prependToLeft}
|
|
# * {::appendToLeft}
|
|
# * {::prependToRight}
|
|
# * {::appendToRight}
|
|
#
|
|
# ## Requiring in package specs
|
|
#
|
|
# If you need a `WorkspaceView` instance to test your package, require it via
|
|
# the built-in `atom` module.
|
|
#
|
|
# ```coffee
|
|
# {WorkspaceView} = require 'atom'
|
|
# ```
|
|
#
|
|
# You can assign it to the `atom.workspaceView` global in the spec or just use
|
|
# it as a local, depending on what you're trying to accomplish. Building the
|
|
# `WorkspaceView` is currently expensive, so you should try build a {Workspace}
|
|
# instead if possible.
|
|
module.exports =
|
|
class WorkspaceView extends View
|
|
Delegator.includeInto(this)
|
|
|
|
@delegatesProperty 'fullScreen', 'destroyedItemURIs', toProperty: 'model'
|
|
@delegatesMethods 'open', 'openSync',
|
|
'saveActivePaneItem', 'saveActivePaneItemAs', 'saveAll', 'destroyActivePaneItem',
|
|
'destroyActivePane', 'increaseFontSize', 'decreaseFontSize', toProperty: 'model'
|
|
|
|
constructor: (@element) ->
|
|
unless @element?
|
|
return atom.views.getView(atom.workspace).__spacePenView
|
|
super
|
|
@deprecateViewEvents()
|
|
@attachedEditorViews = new WeakSet
|
|
|
|
setModel: (@model) ->
|
|
@horizontal = @find('atom-workspace-axis.horizontal')
|
|
@vertical = @find('atom-workspace-axis.vertical')
|
|
@panes = @find('atom-pane-container').view()
|
|
@subscribe @model.onDidOpen => @trigger 'uri-opened'
|
|
|
|
beforeRemove: ->
|
|
@model?.destroy()
|
|
|
|
###
|
|
Section: Accessing the Workspace Model
|
|
###
|
|
|
|
# Essential: Get the underlying model object.
|
|
#
|
|
# Returns a {Workspace}.
|
|
getModel: -> @model
|
|
|
|
###
|
|
Section: Accessing Views
|
|
###
|
|
|
|
# Essential: Register a function to be called for every current and future
|
|
# editor view in the workspace (only includes {TextEditorView}s that are pane
|
|
# items).
|
|
#
|
|
# * `callback` A {Function} with an {TextEditorView} as its only argument.
|
|
# * `editorView` {TextEditorView}
|
|
#
|
|
# Returns a subscription object with an `.off` method that you can call to
|
|
# unregister the callback.
|
|
eachEditorView: (callback) ->
|
|
for editorView in @getEditorViews()
|
|
@attachedEditorViews.add(editorView)
|
|
callback(editorView)
|
|
|
|
attachedCallback = (e, editorView) =>
|
|
unless @attachedEditorViews.has(editorView)
|
|
@attachedEditorViews.add(editorView)
|
|
callback(editorView) unless editorView.mini
|
|
|
|
@on('editor:attached', attachedCallback)
|
|
|
|
off: => @off('editor:attached', attachedCallback)
|
|
|
|
# Essential: Register a function to be called for every current and future
|
|
# pane view in the workspace.
|
|
#
|
|
# * `callback` A {Function} with a {PaneView} as its only argument.
|
|
# * `paneView` {PaneView}
|
|
#
|
|
# Returns a subscription object with an `.off` method that you can call to
|
|
# unregister the callback.
|
|
eachPaneView: (callback) ->
|
|
@panes.eachPaneView(callback)
|
|
|
|
# Essential: Get all existing pane views.
|
|
#
|
|
# Prefer {Workspace::getPanes} if you don't need access to the view objects.
|
|
# Also consider using {::eachPaneView} if you want to register a callback for
|
|
# all current and *future* pane views.
|
|
#
|
|
# Returns an Array of all open {PaneView}s.
|
|
getPaneViews: ->
|
|
@panes.getPaneViews()
|
|
|
|
# Essential: Get the active pane view.
|
|
#
|
|
# Prefer {Workspace::getActivePane} if you don't actually need access to the
|
|
# view.
|
|
#
|
|
# Returns a {PaneView}.
|
|
getActivePaneView: ->
|
|
@panes.getActivePaneView()
|
|
|
|
# Essential: Get the view associated with the active pane item.
|
|
#
|
|
# Returns a view.
|
|
getActiveView: ->
|
|
@panes.getActiveView()
|
|
|
|
###
|
|
Section: Adding elements to the workspace
|
|
###
|
|
|
|
prependToTop: (element) ->
|
|
deprecate 'Please use Workspace::addTopPanel() instead'
|
|
@vertical.prepend(element)
|
|
|
|
appendToTop: (element) ->
|
|
deprecate 'Please use Workspace::addTopPanel() instead'
|
|
@panes.before(element)
|
|
|
|
prependToBottom: (element) ->
|
|
deprecate 'Please use Workspace::addBottomPanel() instead'
|
|
@panes.after(element)
|
|
|
|
appendToBottom: (element) ->
|
|
deprecate 'Please use Workspace::addBottomPanel() instead'
|
|
@vertical.append(element)
|
|
|
|
prependToLeft: (element) ->
|
|
deprecate 'Please use Workspace::addLeftPanel() instead'
|
|
@horizontal.prepend(element)
|
|
|
|
appendToLeft: (element) ->
|
|
deprecate 'Please use Workspace::addLeftPanel() instead'
|
|
@vertical.before(element)
|
|
|
|
prependToRight: (element) ->
|
|
deprecate 'Please use Workspace::addRightPanel() instead'
|
|
@vertical.after(element)
|
|
|
|
appendToRight: (element) ->
|
|
deprecate 'Please use Workspace::addRightPanel() instead'
|
|
@horizontal.append(element)
|
|
|
|
###
|
|
Section: Focusing pane views
|
|
###
|
|
|
|
# Focus the previous pane by id.
|
|
focusPreviousPaneView: -> @model.activatePreviousPane()
|
|
|
|
# Focus the next pane by id.
|
|
focusNextPaneView: -> @model.activateNextPane()
|
|
|
|
# Focus the pane directly above the active pane.
|
|
focusPaneViewAbove: -> @panes.focusPaneViewAbove()
|
|
|
|
# Focus the pane directly below the active pane.
|
|
focusPaneViewBelow: -> @panes.focusPaneViewBelow()
|
|
|
|
# Focus the pane directly to the left of the active pane.
|
|
focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft()
|
|
|
|
# Focus the pane directly to the right of the active pane.
|
|
focusPaneViewOnRight: -> @panes.focusPaneViewOnRight()
|
|
|
|
###
|
|
Section: Private
|
|
###
|
|
|
|
# Prompts to save all unsaved items
|
|
confirmClose: ->
|
|
@model.confirmClose()
|
|
|
|
# Get all editor views.
|
|
#
|
|
# You should prefer {Workspace::getEditors} unless you absolutely need access
|
|
# to the view objects. Also consider using {::eachEditorView}, which will call
|
|
# a callback for all current and *future* editor views.
|
|
#
|
|
# Returns an {Array} of {TextEditorView}s.
|
|
getEditorViews: ->
|
|
for editorElement in @panes.element.querySelectorAll('atom-pane > .item-views > atom-text-editor')
|
|
$(editorElement).view()
|
|
|
|
|
|
###
|
|
Section: Deprecated
|
|
###
|
|
|
|
deprecateViewEvents: ->
|
|
originalWorkspaceViewOn = @on
|
|
|
|
@on = (eventName) =>
|
|
switch eventName
|
|
when 'beep'
|
|
deprecate('Use Atom::onDidBeep instead')
|
|
when 'cursor:moved'
|
|
deprecate('Use TextEditor::onDidChangeCursorPosition instead')
|
|
when 'editor:attached'
|
|
deprecate('Use Workspace::onDidAddTextEditor instead')
|
|
when 'editor:detached'
|
|
deprecate('Use TextEditor::onDidDestroy instead')
|
|
when 'editor:will-be-removed'
|
|
deprecate('Use TextEditor::onDidDestroy instead')
|
|
when 'pane:active-item-changed'
|
|
deprecate('Use Pane::onDidChangeActiveItem instead')
|
|
when 'pane:active-item-modified-status-changed'
|
|
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead')
|
|
when 'pane:active-item-title-changed'
|
|
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead')
|
|
when 'pane:attached'
|
|
deprecate('Use Workspace::onDidAddPane instead')
|
|
when 'pane:became-active'
|
|
deprecate('Use Pane::onDidActivate instead')
|
|
when 'pane:became-inactive'
|
|
deprecate('Use Pane::onDidChangeActive instead')
|
|
when 'pane:item-added'
|
|
deprecate('Use Pane::onDidAddItem instead')
|
|
when 'pane:item-moved'
|
|
deprecate('Use Pane::onDidMoveItem instead')
|
|
when 'pane:item-removed'
|
|
deprecate('Use Pane::onDidRemoveItem instead')
|
|
when 'pane:removed'
|
|
deprecate('Use Pane::onDidDestroy instead')
|
|
when 'pane-container:active-pane-item-changed'
|
|
deprecate('Use Workspace::onDidChangeActivePaneItem instead')
|
|
when 'selection:changed'
|
|
deprecate('Use TextEditor::onDidChangeSelectionRange instead')
|
|
when 'uri-opened'
|
|
deprecate('Use Workspace::onDidOpen instead')
|
|
originalWorkspaceViewOn.apply(this, arguments)
|
|
|
|
TextEditorView = require './text-editor-view'
|
|
originalEditorViewOn = TextEditorView::on
|
|
TextEditorView::on = (eventName) ->
|
|
switch eventName
|
|
when 'cursor:moved'
|
|
deprecate('Use TextEditor::onDidChangeCursorPosition instead')
|
|
when 'editor:attached'
|
|
deprecate('Use TextEditor::onDidAddTextEditor instead')
|
|
when 'editor:detached'
|
|
deprecate('Use TextEditor::onDidDestroy instead')
|
|
when 'editor:will-be-removed'
|
|
deprecate('Use TextEditor::onDidDestroy instead')
|
|
when 'selection:changed'
|
|
deprecate('Use TextEditor::onDidChangeSelectionRange instead')
|
|
originalEditorViewOn.apply(this, arguments)
|
|
|
|
originalPaneViewOn = PaneView::on
|
|
PaneView::on = (eventName) ->
|
|
switch eventName
|
|
when 'cursor:moved'
|
|
deprecate('Use TextEditor::onDidChangeCursorPosition instead')
|
|
when 'editor:attached'
|
|
deprecate('Use TextEditor::onDidAddTextEditor instead')
|
|
when 'editor:detached'
|
|
deprecate('Use TextEditor::onDidDestroy instead')
|
|
when 'editor:will-be-removed'
|
|
deprecate('Use TextEditor::onDidDestroy instead')
|
|
when 'pane:active-item-changed'
|
|
deprecate('Use Pane::onDidChangeActiveItem instead')
|
|
when 'pane:active-item-modified-status-changed'
|
|
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead')
|
|
when 'pane:active-item-title-changed'
|
|
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead')
|
|
when 'pane:attached'
|
|
deprecate('Use Workspace::onDidAddPane instead')
|
|
when 'pane:became-active'
|
|
deprecate('Use Pane::onDidActivate instead')
|
|
when 'pane:became-inactive'
|
|
deprecate('Use Pane::onDidChangeActive instead')
|
|
when 'pane:item-added'
|
|
deprecate('Use Pane::onDidAddItem instead')
|
|
when 'pane:item-moved'
|
|
deprecate('Use Pane::onDidMoveItem instead')
|
|
when 'pane:item-removed'
|
|
deprecate('Use Pane::onDidRemoveItem instead')
|
|
when 'pane:removed'
|
|
deprecate('Use Pane::onDidDestroy instead')
|
|
when 'selection:changed'
|
|
deprecate('Use TextEditor::onDidChangeSelectionRange instead')
|
|
originalPaneViewOn.apply(this, arguments)
|
|
|
|
# Deprecated
|
|
eachPane: (callback) ->
|
|
deprecate("Use WorkspaceView::eachPaneView instead")
|
|
@eachPaneView(callback)
|
|
|
|
# Deprecated
|
|
getPanes: ->
|
|
deprecate("Use WorkspaceView::getPaneViews instead")
|
|
@getPaneViews()
|
|
|
|
# Deprecated
|
|
getActivePane: ->
|
|
deprecate("Use WorkspaceView::getActivePaneView instead")
|
|
@getActivePaneView()
|
|
|
|
# Deprecated: Call {Workspace::getActivePaneItem} instead.
|
|
getActivePaneItem: ->
|
|
deprecate("Use Workspace::getActivePaneItem instead")
|
|
@model.getActivePaneItem()
|