2015-05-20 07:06:59 +08:00
|
|
|
_ = require 'underscore'
|
2015-09-14 22:37:00 +08:00
|
|
|
React = require 'react'
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
{File,
|
|
|
|
Utils,
|
2015-03-14 03:55:52 +08:00
|
|
|
Actions,
|
2015-12-11 05:51:44 +08:00
|
|
|
DOMUtils,
|
2015-03-03 03:23:35 +08:00
|
|
|
DraftStore,
|
2015-09-14 22:37:00 +08:00
|
|
|
UndoManager,
|
2015-08-04 04:06:28 +08:00
|
|
|
ContactStore,
|
feat(accounts): Kill namespaces, long live accounts
Summary:
This diff replaces the Namespace object with the Account object, and changes all references to namespace_id => account_id, etc. The endpoints are now `/threads` instead of `/n/<id>/threads`.
This diff also adds preliminary support for multiple accounts. When you log in, we now log you in to all the attached accounts on edgehill server. From the preferences panel, you can auth with / unlink additional accounts. Shockingly, this all seems to pretty much work.
When replying to a thread, you cannot switch from addresses. However, when creating a new message in a popout composer, you can change the from address and the SaveDraftTask will delete/re-root the draft on the new account.
Search bar doesn't need to do full refresh on clear if it never committed
Allow drafts to be switched to a different account when not in reply to an existing thread
Fix edge case where ChangeMailTask throws exception if no models are modified during performLocal
Show many dots for many accounts in long polling status bar
add/remove accounts from prefs
Spec fixes!
Test Plan: Run tests, none broken!
Reviewers: evan, dillon
Reviewed By: evan, dillon
Differential Revision: https://phab.nylas.com/D1928
2015-08-22 06:29:58 +08:00
|
|
|
AccountStore,
|
2015-06-12 02:52:49 +08:00
|
|
|
FileUploadStore,
|
2015-12-08 07:34:03 +08:00
|
|
|
QuotedHTMLTransformer,
|
2015-11-28 03:49:24 +08:00
|
|
|
FileDownloadStore,
|
2015-12-30 01:15:18 +08:00
|
|
|
FocusedContentStore,
|
2015-11-28 03:49:24 +08:00
|
|
|
ExtensionRegistry} = require 'nylas-exports'
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
{DropZone,
|
|
|
|
RetinaImg,
|
|
|
|
ScrollRegion,
|
2015-04-25 02:33:10 +08:00
|
|
|
InjectedComponent,
|
2015-11-07 03:47:06 +08:00
|
|
|
KeyCommandsRegion,
|
2015-09-14 22:37:00 +08:00
|
|
|
InjectedComponentSet} = require 'nylas-component-kit'
|
2015-02-28 07:34:37 +08:00
|
|
|
|
2015-06-12 02:52:49 +08:00
|
|
|
FileUpload = require './file-upload'
|
|
|
|
ImageFileUpload = require './image-file-upload'
|
2015-09-14 22:37:00 +08:00
|
|
|
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
ComposerEditor = require './composer-editor'
|
2016-01-26 06:14:09 +08:00
|
|
|
SendActionButton = require './send-action-button'
|
2015-09-14 22:37:00 +08:00
|
|
|
ExpandedParticipants = require './expanded-participants'
|
|
|
|
CollapsedParticipants = require './collapsed-participants'
|
|
|
|
|
|
|
|
Fields = require './fields'
|
2015-08-06 08:38:44 +08:00
|
|
|
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
# The ComposerView is a unique React component because it (currently) is a
|
|
|
|
# singleton. Normally, the React way to do things would be to re-render the
|
2015-05-01 02:35:38 +08:00
|
|
|
# Composer with new props.
|
2015-05-01 04:08:29 +08:00
|
|
|
class ComposerView extends React.Component
|
|
|
|
@displayName: 'ComposerView'
|
|
|
|
|
feat(unsafe-components): Wrap injected components, catch exceptions, clean up ComponentRegistry
Summary:
This diff gives the ComponentRegistry a cleaner, smaller API. Instead of querying by name, location or role,
it's now just location and role, and you can register components for one or more location and one or more
roles without assigning the entries in the registry separate names.
When you register with the ComponentRegistry, the syntax is also cleaner and uses the component's displayName
instead of requiring you to provide a name. You also provide the actual component when unregistering, ensuring
that you can't unregister someone else's component.
InjectedComponent and InjectedComponentSet now wrap their children in UnsafeComponent, which prevents
render/component lifecycle problems from propogating.
Existing components have been updated:
1. maxWidth / minWidth are now containerStyles.maxWidth/minWidth
2. displayName is now required to use the CR.
3. containerRequired = false can be provided to exempt a component from being wrapped in an UnsafeComponent.
This is useful because it's slightly faster and keeps DOM flat.
This diff also makes the "Show Component Regions" more awesome. It displays column regions, since they now
use the InjectedComponentSet, and also shows for InjectedComponent as well as InjectedComponentSet.
Change ComponentRegistry syntax, lots more work on safely wrapping items. See description.
Fix for inline flexbox scenarios (message actions)
Allow ~/.inbox/packages to be symlinked to a github repo
Test Plan: Run tests!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1457
2015-05-01 07:10:15 +08:00
|
|
|
@containerRequired: false
|
|
|
|
|
2015-05-08 06:28:44 +08:00
|
|
|
@propTypes:
|
2015-11-24 14:09:17 +08:00
|
|
|
draftClientId: React.PropTypes.string
|
2015-05-08 06:28:44 +08:00
|
|
|
|
|
|
|
# Either "inline" or "fullwindow"
|
|
|
|
mode: React.PropTypes.string
|
|
|
|
|
|
|
|
# If this composer is part of an existing thread (like inline
|
|
|
|
# composers) the threadId will be handed down
|
|
|
|
threadId: React.PropTypes.string
|
|
|
|
|
|
|
|
# Sometimes when changes in the composer happens it's desirable to
|
|
|
|
# have the parent scroll to a certain location. A parent component can
|
|
|
|
# pass a callback that gets called when this composer wants to be
|
|
|
|
# scrolled to.
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
scrollTo: React.PropTypes.func
|
2015-05-08 06:28:44 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
constructor: (@props) ->
|
|
|
|
@state =
|
|
|
|
populated: false
|
|
|
|
to: []
|
|
|
|
cc: []
|
|
|
|
bcc: []
|
2016-01-26 10:20:19 +08:00
|
|
|
from: []
|
2015-05-01 04:08:29 +08:00
|
|
|
body: ""
|
2015-06-12 02:52:49 +08:00
|
|
|
files: []
|
2016-01-28 16:33:22 +08:00
|
|
|
uploads: []
|
2015-05-01 04:08:29 +08:00
|
|
|
subject: ""
|
2016-01-26 10:20:19 +08:00
|
|
|
accounts: []
|
2015-09-14 22:37:00 +08:00
|
|
|
focusedField: Fields.To # Gets updated in @_initiallyFocusedField
|
|
|
|
enabledFields: [] # Gets updated in @_initiallyEnabledFields
|
2015-05-01 04:08:29 +08:00
|
|
|
showQuotedText: false
|
|
|
|
|
|
|
|
componentWillMount: =>
|
2015-08-29 02:12:53 +08:00
|
|
|
@_prepareForDraft(@props.draftClientId)
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-07-16 22:41:04 +08:00
|
|
|
shouldComponentUpdate: (nextProps, nextState) =>
|
|
|
|
not Utils.isEqualReact(nextProps, @props) or
|
|
|
|
not Utils.isEqualReact(nextState, @state)
|
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
componentDidMount: =>
|
2015-09-14 22:37:00 +08:00
|
|
|
@_usubs = []
|
|
|
|
@_usubs.push AccountStore.listen @_onAccountStoreChanged
|
2015-11-19 04:09:07 +08:00
|
|
|
@_applyFieldFocus()
|
2015-02-11 03:10:14 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
componentWillUnmount: =>
|
2015-05-16 01:45:18 +08:00
|
|
|
@_unmounted = true # rarf
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
@_teardownForDraft()
|
feat(signatures): Initial signature support
Summary:
- Draft Store extensions can now implement `prepareNewDraft` to have an opportunity to change a draft before it's displayed for the first time.
- When composers are torn down, they delete their draft if it is still pristine. This makes the behavior of closing unedited popout drafts the same as leaving unedited inline drafts.
- The DraftStoreProxy keeps the initial body of the draft *if* it started in a pristine state. This means "is the body empty" is just a simple == check, and it takes into account anything added to the body by extensions.
- Calling Actions.destroyDraft doesn't blow up anymore if the draft session can't be found. This was a bug and meant that you couldn't destroy drafts which hadn't been previously edited, and also meant that bad things(tm) happened when you called destroyDraft twice, which seemed like overkill.
- DestroyDraft task now exits gracefully when the draft cannot be found.
You can test this feature by adding the following to your config.cson:
```
signatures:
NAMESPACEID: "<br/><br/><div id=\"Signature\"><div id=\"divtagdefaultwrapper\" style=\"font-size:12pt; color:#000000; background-color:#FFFFFF; font-family:Calibri,Arial,Helvetica,sans-serif\"><p></p><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"450\" style=\"font-family:'Times New Roman'; table-layout:fixed\"><tbody><tr><td class=\"logo-td\" align=\"left\" valign=\"top\" width=\"76\"><p style=\"margin-bottom:10px; margin-right:10px; font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px\"><a href=\"http://www.nylas.com/\" class=\"clink logo-container\" style=\"text-decoration:none\"><img alt=\"Nylas\" border=\"0\" class=\"sig-logo\" height=\"80\" width=\"66\" style=\"-webkit-user-select: none;\" src=\"https://s3-us-west-2.amazonaws.com/nylas-static-assets/nylas-email-signature.png\"></a></p><p class=\"social-list\" style=\"font-size:0px; line-height:0; font-family:Helvetica,Arial,sans-serif\"></p></td><td align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"spacer-td\" width=\"16\" style=\"border-left-width:2px; border-left-style:solid; border-left-color:rgb(30,162,162)\"><img width=\"10\" style=\"-webkit-user-select: none;\" src=\"https://s3.amazonaws.com/htmlsig-assets/spacer.gif\"></td><td align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"content-td\" width=\"368\"><div class=\"content-pad\"><p style=\"font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px; color:rgb(33,33,33); margin-bottom:10px\"><span class=\"txt signature_name-target sig-hide\" style=\"font-weight:bold; display:inline\">Gleb Polyakov</span> <span class=\"email-sep break\" style=\"display:inline\"><br></span><a class=\"link email signature_email-target sig-hide\" href=\"mailto:gleb@nylas.com\" style=\"color:rgb(30,162,162); text-decoration:none; display:inline\">gleb@nylas.com</a><span class=\"signature_email-sep sep\" style=\"display:inline\"> / </span><span class=\"txt signature_mobilephone-target sig-hide\" style=\"display:inline\">404-786-4100</span></p><p style=\"font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px; margin-bottom:10px\"><span class=\"txt signature_companyname-target sig-hide\" style=\"font-weight:bold; color:rgb(33,33,33); display:inline\">Nylas</span> <span class=\"company-sep break\" style=\"display:inline\"><br></span><span class=\"address-sep break\"></span><span class=\"address2-sep break\"></span><span class=\"website-sep break\"></span><a class=\"link signature_website-target sig-hide\" href=\"http://www.nylas.com/\" style=\"color:rgb(30,162,162); text-decoration:none; display:inline\">http://www.nylas.com</a></p></div></td></tr><tr><td colspan=\"3\"></td></tr><tr><td colspan=\"3\"></td></tr><tr><td colspan=\"3\"><p class=\"txt signature_disclaimer-target\" style=\"font-family:Helvetica,Arial,sans-serif; color:rgb(33,33,33); font-size:9px; line-height:12px; margin-top:10px\"></p></td></tr></tbody></table><p></p></div></div>"
```
specs for draft store extension hooks, some draft store refactoring
Test Plan: Run a few new specs that make sure extensions are run
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1741
2015-07-15 03:20:06 +08:00
|
|
|
@_deleteDraftIfEmpty()
|
2015-09-14 22:37:00 +08:00
|
|
|
usub() for usub in @_usubs
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-12-30 01:15:18 +08:00
|
|
|
componentDidUpdate: (prevProps, prevState) =>
|
2015-03-03 07:33:58 +08:00
|
|
|
# We want to use a temporary variable instead of putting this into the
|
|
|
|
# state. This is because the selection is a transient property that
|
|
|
|
# only needs to be applied once. It's not a long-living property of
|
|
|
|
# the state. We could call `setState` here, but this saves us from a
|
|
|
|
# re-rendering.
|
|
|
|
@_recoveredSelection = null if @_recoveredSelection?
|
|
|
|
|
2015-12-30 01:15:18 +08:00
|
|
|
# If the body changed, let's wait for the editor body to actually get rendered
|
|
|
|
# into the dom before applying focus.
|
|
|
|
# Since the editor is an InjectedComponent, when this function gets called
|
|
|
|
# the editor hasn't actually finished rendering, so we need to wait for that
|
|
|
|
# to happen by using the InjectedComponent's `onComponentDidRender` callback.
|
|
|
|
# See `_renderEditor`
|
|
|
|
bodyChanged = @state.body isnt prevState.body
|
|
|
|
return if bodyChanged
|
2015-11-19 04:09:07 +08:00
|
|
|
@_applyFieldFocus()
|
2015-09-14 22:37:00 +08:00
|
|
|
|
2016-01-06 03:34:26 +08:00
|
|
|
focus: =>
|
|
|
|
@_applyFieldFocus()
|
|
|
|
|
2015-11-07 03:47:06 +08:00
|
|
|
_keymapHandlers: ->
|
2016-01-26 06:14:09 +08:00
|
|
|
'composer:send-message': => @_onPrimarySend()
|
2015-11-07 03:47:06 +08:00
|
|
|
'composer:delete-empty-draft': => @_deleteDraftIfEmpty()
|
|
|
|
'composer:show-and-focus-bcc': =>
|
2015-12-09 09:44:20 +08:00
|
|
|
@_onAdjustEnabledFields(show: [Fields.Bcc])
|
2015-11-07 03:47:06 +08:00
|
|
|
'composer:show-and-focus-cc': =>
|
2015-12-09 09:44:20 +08:00
|
|
|
@_onAdjustEnabledFields(show: [Fields.Cc])
|
2015-11-07 03:47:06 +08:00
|
|
|
'composer:focus-to': =>
|
2015-12-09 09:44:20 +08:00
|
|
|
@_onAdjustEnabledFields(show: [Fields.To])
|
2015-11-07 03:47:06 +08:00
|
|
|
"composer:show-and-focus-from": => # TODO
|
|
|
|
"composer:undo": @undo
|
|
|
|
"composer:redo": @redo
|
|
|
|
|
2015-12-30 01:15:18 +08:00
|
|
|
_applyFieldFocus: =>
|
2016-01-06 03:34:26 +08:00
|
|
|
@_applyFieldFocusTo(@state.focusedField)
|
2015-06-18 03:29:32 +08:00
|
|
|
|
2016-01-06 03:34:26 +08:00
|
|
|
_applyFieldFocusTo: (fieldName) =>
|
|
|
|
return unless @refs[fieldName]
|
|
|
|
|
|
|
|
$el = React.findDOMNode(@refs[fieldName])
|
|
|
|
return if document.activeElement is $el or $el.contains(document.activeElement)
|
|
|
|
if @refs[fieldName].focus
|
|
|
|
@refs[fieldName].focus()
|
|
|
|
else
|
2016-01-12 10:13:42 +08:00
|
|
|
$el.select()
|
2016-01-06 03:34:26 +08:00
|
|
|
$el.focus()
|
2015-11-19 04:09:07 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
componentWillReceiveProps: (newProps) =>
|
2015-07-16 22:41:04 +08:00
|
|
|
@_ignoreNextTrigger = false
|
2015-08-29 02:12:53 +08:00
|
|
|
if newProps.draftClientId isnt @props.draftClientId
|
|
|
|
# When we're given a new draft draftClientId, we have to stop listening to our
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
# current DraftStoreProxy, create a new one and listen to that. The simplest
|
|
|
|
# way to do this is to just re-call registerListeners.
|
|
|
|
@_teardownForDraft()
|
2015-08-29 02:12:53 +08:00
|
|
|
@_prepareForDraft(newProps.draftClientId)
|
2015-03-24 07:33:28 +08:00
|
|
|
|
2015-08-29 02:12:53 +08:00
|
|
|
_prepareForDraft: (draftClientId) =>
|
2015-03-24 07:33:28 +08:00
|
|
|
@unlisteners = []
|
2015-08-29 02:12:53 +08:00
|
|
|
return unless draftClientId
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-03-06 10:49:43 +08:00
|
|
|
# UndoManager must be ready before we call _onDraftChanged for the first time
|
|
|
|
@undoManager = new UndoManager
|
2015-08-29 02:12:53 +08:00
|
|
|
DraftStore.sessionForClientId(draftClientId).then(@_setupSession)
|
2015-05-16 01:45:18 +08:00
|
|
|
|
2015-11-07 04:08:32 +08:00
|
|
|
_setupSession: (proxy) =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return if @_unmounted
|
2015-08-29 02:12:53 +08:00
|
|
|
return unless proxy.draftClientId is @props.draftClientId
|
2015-05-16 01:45:18 +08:00
|
|
|
@_proxy = proxy
|
2015-06-12 02:52:49 +08:00
|
|
|
@_preloadImages(@_proxy.draft()?.files)
|
2015-03-24 07:33:28 +08:00
|
|
|
@unlisteners.push @_proxy.listen(@_onDraftChanged)
|
2015-05-16 01:45:18 +08:00
|
|
|
@_onDraftChanged()
|
2015-03-03 07:33:58 +08:00
|
|
|
|
2015-06-12 02:52:49 +08:00
|
|
|
_preloadImages: (files=[]) ->
|
|
|
|
files.forEach (file) ->
|
2016-01-27 07:36:13 +08:00
|
|
|
if Utils.shouldDisplayAsImage(file)
|
2015-06-12 02:52:49 +08:00
|
|
|
Actions.fetchFile(file)
|
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_teardownForDraft: =>
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
unlisten() for unlisten in @unlisteners
|
2015-03-24 07:33:28 +08:00
|
|
|
if @_proxy
|
|
|
|
@_proxy.changes.commit()
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-11-07 03:47:06 +08:00
|
|
|
render: ->
|
2016-01-06 03:34:26 +08:00
|
|
|
classes = "message-item-white-wrap composer-outer-wrap #{@props.className ? ""}"
|
|
|
|
|
|
|
|
<KeyCommandsRegion
|
|
|
|
localHandlers={@_keymapHandlers()}
|
|
|
|
className={classes}
|
|
|
|
onFocusIn={@_onFocusIn}
|
|
|
|
tabIndex="-1"
|
|
|
|
ref="composerWrap">
|
|
|
|
{@_renderComposer()}
|
2015-11-07 03:47:06 +08:00
|
|
|
</KeyCommandsRegion>
|
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_renderComposer: =>
|
2015-07-24 02:10:51 +08:00
|
|
|
<DropZone className="composer-inner-wrap"
|
|
|
|
shouldAcceptDrop={@_shouldAcceptDrop}
|
|
|
|
onDragStateChange={ ({isDropping}) => @setState({isDropping}) }
|
|
|
|
onDrop={@_onDrop}>
|
2015-07-16 11:06:11 +08:00
|
|
|
<div className="composer-drop-cover" style={display: if @state.isDropping then 'block' else 'none'}>
|
|
|
|
<div className="centered">
|
|
|
|
<RetinaImg name="composer-drop-to-attach.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
|
|
|
Drop to attach
|
|
|
|
</div>
|
2015-03-13 05:48:56 +08:00
|
|
|
</div>
|
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
<div className="composer-content-wrap" onKeyDown={@_onKeyDown}>
|
|
|
|
{@_renderScrollRegion()}
|
2015-03-05 07:52:40 +08:00
|
|
|
|
2015-06-06 02:02:44 +08:00
|
|
|
</div>
|
|
|
|
<div className="composer-action-bar-wrap">
|
|
|
|
{@_renderActionsRegion()}
|
|
|
|
</div>
|
2015-07-24 02:10:51 +08:00
|
|
|
</DropZone>
|
2015-03-06 05:31:11 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
_renderScrollRegion: ->
|
|
|
|
if @props.mode is "inline"
|
|
|
|
@_renderContent()
|
|
|
|
else
|
|
|
|
<ScrollRegion className="compose-body-scroll" ref="scrollregion">
|
|
|
|
{@_renderContent()}
|
|
|
|
</ScrollRegion>
|
|
|
|
|
2015-12-10 04:35:40 +08:00
|
|
|
_renderContent: =>
|
2015-09-14 22:37:00 +08:00
|
|
|
<div className="composer-centered">
|
|
|
|
{if @state.focusedField in Fields.ParticipantFields
|
2015-09-23 07:02:44 +08:00
|
|
|
<ExpandedParticipants
|
|
|
|
to={@state.to} cc={@state.cc} bcc={@state.bcc}
|
|
|
|
from={@state.from}
|
|
|
|
ref="expandedParticipants"
|
|
|
|
mode={@props.mode}
|
2016-01-26 10:20:19 +08:00
|
|
|
accounts={@state.accounts}
|
2015-09-23 07:02:44 +08:00
|
|
|
focusedField={@state.focusedField}
|
|
|
|
enabledFields={@state.enabledFields}
|
|
|
|
onPopoutComposer={@_onPopoutComposer}
|
|
|
|
onChangeParticipants={@_onChangeParticipants}
|
2015-12-09 09:44:20 +08:00
|
|
|
onChangeFocusedField={@_onChangeFocusedField}
|
|
|
|
onAdjustEnabledFields={@_onAdjustEnabledFields} />
|
2015-09-14 22:37:00 +08:00
|
|
|
else
|
2015-09-23 07:02:44 +08:00
|
|
|
<CollapsedParticipants
|
|
|
|
to={@state.to} cc={@state.cc} bcc={@state.bcc}
|
2015-12-09 09:44:20 +08:00
|
|
|
onClick={@_onExpandParticipantFields} />
|
2015-09-14 22:37:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
{@_renderSubject()}
|
|
|
|
|
2015-11-19 04:09:07 +08:00
|
|
|
<div className="compose-body"
|
|
|
|
ref="composeBody"
|
|
|
|
onMouseUp={@_onMouseUpComposerBody}
|
|
|
|
onMouseDown={@_onMouseDownComposerBody}>
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
{@_renderBodyRegions()}
|
2015-09-14 22:37:00 +08:00
|
|
|
{@_renderFooterRegions()}
|
2015-08-11 05:47:47 +08:00
|
|
|
</div>
|
2015-09-14 22:37:00 +08:00
|
|
|
</div>
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-09-23 07:02:44 +08:00
|
|
|
_onPopoutComposer: =>
|
|
|
|
Actions.composePopoutDraft @props.draftClientId
|
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
_onKeyDown: (event) =>
|
|
|
|
if event.key is "Tab"
|
|
|
|
@_onTabDown(event)
|
|
|
|
return
|
feat(accounts): Kill namespaces, long live accounts
Summary:
This diff replaces the Namespace object with the Account object, and changes all references to namespace_id => account_id, etc. The endpoints are now `/threads` instead of `/n/<id>/threads`.
This diff also adds preliminary support for multiple accounts. When you log in, we now log you in to all the attached accounts on edgehill server. From the preferences panel, you can auth with / unlink additional accounts. Shockingly, this all seems to pretty much work.
When replying to a thread, you cannot switch from addresses. However, when creating a new message in a popout composer, you can change the from address and the SaveDraftTask will delete/re-root the draft on the new account.
Search bar doesn't need to do full refresh on clear if it never committed
Allow drafts to be switched to a different account when not in reply to an existing thread
Fix edge case where ChangeMailTask throws exception if no models are modified during performLocal
Show many dots for many accounts in long polling status bar
add/remove accounts from prefs
Spec fixes!
Test Plan: Run tests, none broken!
Reviewers: evan, dillon
Reviewed By: evan, dillon
Differential Revision: https://phab.nylas.com/D1928
2015-08-22 06:29:58 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
_onTabDown: (event) =>
|
|
|
|
event.preventDefault()
|
2015-12-09 09:44:20 +08:00
|
|
|
event.stopPropagation()
|
|
|
|
@_onShiftFocusedField(if event.shiftKey then -1 else 1)
|
|
|
|
|
|
|
|
_onShiftFocusedField: (dir) =>
|
2015-09-14 22:37:00 +08:00
|
|
|
enabledFields = _.filter @state.enabledFields, (field) ->
|
|
|
|
Fields.Order[field] >= 0
|
|
|
|
enabledFields = _.sortBy enabledFields, (field) ->
|
|
|
|
Fields.Order[field]
|
|
|
|
i = enabledFields.indexOf @state.focusedField
|
|
|
|
newI = Math.min(Math.max(i + dir, 0), enabledFields.length - 1)
|
2015-12-09 09:44:20 +08:00
|
|
|
@_onChangeFocusedField(enabledFields[newI])
|
2015-04-25 02:33:10 +08:00
|
|
|
|
2015-12-09 09:44:20 +08:00
|
|
|
_onChangeFocusedField: (focusedField) =>
|
2016-01-09 10:45:26 +08:00
|
|
|
@setState({focusedField})
|
|
|
|
if focusedField in Fields.ParticipantFields
|
2015-12-09 09:44:20 +08:00
|
|
|
@_lastFocusedParticipantField = focusedField
|
2015-09-14 22:37:00 +08:00
|
|
|
|
2015-12-09 09:44:20 +08:00
|
|
|
_onExpandParticipantFields: =>
|
|
|
|
@_onChangeFocusedField(@_lastFocusedParticipantField ? Fields.To)
|
2015-09-14 22:37:00 +08:00
|
|
|
|
2015-12-09 09:44:20 +08:00
|
|
|
_onAdjustEnabledFields: ({show, hide}={}) =>
|
2015-09-14 22:37:00 +08:00
|
|
|
show = show ? []; hide = hide ? []
|
2015-12-09 09:44:20 +08:00
|
|
|
enabledFields = _.difference(@state.enabledFields.concat(show), hide)
|
|
|
|
|
|
|
|
if hide.length > 0 and enabledFields.indexOf(@state.focusedField) is -1
|
|
|
|
@_onShiftFocusedField(-1)
|
|
|
|
|
|
|
|
@setState({enabledFields})
|
|
|
|
|
|
|
|
if show.length > 0
|
|
|
|
@_onChangeFocusedField(show[0])
|
2015-09-14 22:37:00 +08:00
|
|
|
|
|
|
|
_renderSubject: ->
|
|
|
|
if Fields.Subject in @state.enabledFields
|
|
|
|
<div key="subject-wrap" className="compose-subject-wrap">
|
|
|
|
<input type="text"
|
|
|
|
name="subject"
|
|
|
|
ref={Fields.Subject}
|
|
|
|
placeholder="Subject"
|
|
|
|
value={@state.subject}
|
2016-01-09 10:45:26 +08:00
|
|
|
onFocus={ => @setState(focusedField: Fields.Subject) }
|
2015-09-14 22:37:00 +08:00
|
|
|
onChange={@_onChangeSubject}/>
|
|
|
|
</div>
|
2015-06-24 06:21:25 +08:00
|
|
|
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
_renderBodyRegions: =>
|
2015-10-31 08:03:33 +08:00
|
|
|
<span ref="composerBodyWrap">
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
{@_renderEditor()}
|
2015-10-31 08:03:33 +08:00
|
|
|
{@_renderQuotedTextControl()}
|
2015-09-14 22:37:00 +08:00
|
|
|
{@_renderAttachments()}
|
|
|
|
</span>
|
2015-06-24 06:21:25 +08:00
|
|
|
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
_renderEditor: ->
|
|
|
|
exposedProps =
|
|
|
|
body: @_removeQuotedText(@state.body)
|
|
|
|
draftClientId: @props.draftClientId
|
|
|
|
parentActions: {
|
|
|
|
getComposerBoundingRect: @_getComposerBoundingRect
|
|
|
|
scrollTo: @props.scrollTo
|
|
|
|
}
|
|
|
|
initialSelectionSnapshot: @_recoveredSelection
|
2016-01-09 10:45:26 +08:00
|
|
|
onFocus: => @setState(focusedField: Fields.Body)
|
|
|
|
onBlur: => @setState(focusedField: null)
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
onFilePaste: @_onFilePaste
|
|
|
|
onBodyChanged: @_onBodyChanged
|
|
|
|
|
|
|
|
# TODO Get rid of the unecessary required methods:
|
|
|
|
# getCurrentSelection and getPreviousSelection shouldn't be needed and
|
|
|
|
# undo/redo functionality should be refactored into ComposerEditor
|
|
|
|
# _onDOMMutated is just for testing purposes, refactor the tests
|
|
|
|
<InjectedComponent
|
2015-09-23 07:02:44 +08:00
|
|
|
ref={Fields.Body}
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
matching={role: "Composer:Editor"}
|
|
|
|
fallback={ComposerEditor}
|
2015-12-30 01:15:18 +08:00
|
|
|
onComponentDidRender={@_onEditorBodyDidRender}
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
requiredMethods={[
|
2016-01-06 03:34:26 +08:00
|
|
|
'focus'
|
2016-01-13 04:32:59 +08:00
|
|
|
'nativeFocus'
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
'getCurrentSelection'
|
|
|
|
'getPreviousSelection'
|
|
|
|
'_onDOMMutated'
|
|
|
|
]}
|
|
|
|
exposedProps={exposedProps} />
|
|
|
|
|
2015-12-30 01:15:18 +08:00
|
|
|
_onEditorBodyDidRender: =>
|
|
|
|
@_applyFieldFocus()
|
|
|
|
|
2015-10-03 08:14:00 +08:00
|
|
|
# The contenteditable decides when to request a scroll based on the
|
|
|
|
# position of the cursor and its relative distance to this composer
|
|
|
|
# component. We provide it our boundingClientRect so it can calculate
|
|
|
|
# this value.
|
|
|
|
_getComposerBoundingRect: =>
|
2015-11-07 03:47:06 +08:00
|
|
|
React.findDOMNode(@refs.composerWrap).getBoundingClientRect()
|
2015-10-03 08:14:00 +08:00
|
|
|
|
2015-09-23 07:02:44 +08:00
|
|
|
_removeQuotedText: (html) =>
|
|
|
|
if @state.showQuotedText then return html
|
2015-12-08 07:34:03 +08:00
|
|
|
else return QuotedHTMLTransformer.removeQuotedHTML(html)
|
2015-09-23 07:02:44 +08:00
|
|
|
|
|
|
|
_showQuotedText: (html) =>
|
|
|
|
if @state.showQuotedText then return html
|
2015-12-08 07:34:03 +08:00
|
|
|
else return QuotedHTMLTransformer.appendQuotedHTML(html, @state.body)
|
2015-09-23 07:02:44 +08:00
|
|
|
|
|
|
|
_renderQuotedTextControl: ->
|
2015-12-08 07:34:03 +08:00
|
|
|
if QuotedHTMLTransformer.hasQuotedHTML(@state.body)
|
2015-09-23 07:02:44 +08:00
|
|
|
text = if @state.showQuotedText then "Hide" else "Show"
|
|
|
|
<a className="quoted-text-control" onClick={@_onToggleQuotedText}>
|
|
|
|
<span className="dots">•••</span>{text} previous
|
|
|
|
</a>
|
|
|
|
else return []
|
|
|
|
|
|
|
|
_onToggleQuotedText: =>
|
|
|
|
@setState showQuotedText: not @state.showQuotedText
|
2015-08-06 08:38:44 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_renderFooterRegions: =>
|
2015-08-29 02:12:53 +08:00
|
|
|
return <div></div> unless @props.draftClientId
|
2015-04-25 02:33:10 +08:00
|
|
|
|
2015-06-12 02:52:49 +08:00
|
|
|
<div className="composer-footer-region">
|
feat(unsafe-components): Wrap injected components, catch exceptions, clean up ComponentRegistry
Summary:
This diff gives the ComponentRegistry a cleaner, smaller API. Instead of querying by name, location or role,
it's now just location and role, and you can register components for one or more location and one or more
roles without assigning the entries in the registry separate names.
When you register with the ComponentRegistry, the syntax is also cleaner and uses the component's displayName
instead of requiring you to provide a name. You also provide the actual component when unregistering, ensuring
that you can't unregister someone else's component.
InjectedComponent and InjectedComponentSet now wrap their children in UnsafeComponent, which prevents
render/component lifecycle problems from propogating.
Existing components have been updated:
1. maxWidth / minWidth are now containerStyles.maxWidth/minWidth
2. displayName is now required to use the CR.
3. containerRequired = false can be provided to exempt a component from being wrapped in an UnsafeComponent.
This is useful because it's slightly faster and keeps DOM flat.
This diff also makes the "Show Component Regions" more awesome. It displays column regions, since they now
use the InjectedComponentSet, and also shows for InjectedComponent as well as InjectedComponentSet.
Change ComponentRegistry syntax, lots more work on safely wrapping items. See description.
Fix for inline flexbox scenarios (message actions)
Allow ~/.inbox/packages to be symlinked to a github repo
Test Plan: Run tests!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1457
2015-05-01 07:10:15 +08:00
|
|
|
<InjectedComponentSet
|
|
|
|
matching={role: "Composer:Footer"}
|
2015-08-29 02:12:53 +08:00
|
|
|
exposedProps={draftClientId:@props.draftClientId, threadId: @props.threadId}/>
|
2015-06-12 02:52:49 +08:00
|
|
|
</div>
|
|
|
|
|
feat(offline-mode, undo-redo): Tasks handle network errors better and retry, undo/redo based on tasks
Summary:
This diff does a couple things:
- Undo redo with a new undo/redo store that maintains it's own queue of undo/redo tasks. This queue is separate from the TaskQueue because not all tasks should be considered for undo history! Right now just the AddRemoveTagsTask is undoable.
- NylasAPI.makeRequest now returns a promise which resolves with the result or rejects with an error. For things that still need them, there's still `success` and `error` callbacks. I also added `started:(req) ->` which allows you to get the underlying request.
- Aborting a NylasAPI request now makes it call it's error callback / promise reject.
- You can now run code after perform local has completed using this syntax:
```
task = new AddRemoveTagsTask(focused, ['archive'], ['inbox'])
task.waitForPerformLocal().then ->
Actions.setFocus(collection: 'thread', item: nextFocus)
Actions.setCursorPosition(collection: 'thread', item: nextKeyboard)
Actions.queueTask(task)
```
- In specs, you can now use `advanceClock` to get through a Promise.then/catch/finally. Turns out it was using something low level and not using setTimeout(0).
- The TaskQueue uses promises better and defers a lot of the complexity around queueState for performLocal/performRemote to a task subclass called APITask. APITask implements "perform" and breaks it into "performLocal" and "performRemote".
- All tasks either resolve or reject. They're always removed from the queue, unless they resolve with Task.Status.Retry, which means they internally did a .catch (err) => Promise.resolve(Task.Status.Retry) and they want to be run again later.
- API tasks retry until they succeed or receive a NylasAPI.PermanentErrorCode (400,404,500), in which case they revert and finish.
- The AddRemoveTags Task can now take more than one thread! This is super cool because you can undo/redo a bulk action and also because we'll probably have a bulk tag modification API endpoint soon.
Getting undo / redo working revealed that the thread versioning system we built isn't working because the server was incrementing things by more than 1 at a time. Now we count the number of unresolved "optimistic" changes we've made to a given model, and only accept the server's version of it once the number of optimistic changes is back at zero.
Known Issues:
- AddRemoveTagsTasks aren't dependent on each other, so if you (undo/redo x lots) and then come back online, all the tasks try to add / remove all the tags at the same time. To fix this we can either allow the tasks to be merged together into a minimal set or make them block on each other.
- When Offline, you still get errors in the console for GET requests. Need to catch these and display an offline status bar.
- The metadata tasks haven't been updated yet to the new API. Wanted to get it reviewed first!
Test Plan: All the tests still pass!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1694
2015-07-08 01:38:53 +08:00
|
|
|
_renderAttachments: ->
|
|
|
|
<div className="attachments-area">
|
2016-01-27 07:36:13 +08:00
|
|
|
{@_renderFileAttachments()}
|
|
|
|
{@_renderUploadAttachments()}
|
feat(offline-mode, undo-redo): Tasks handle network errors better and retry, undo/redo based on tasks
Summary:
This diff does a couple things:
- Undo redo with a new undo/redo store that maintains it's own queue of undo/redo tasks. This queue is separate from the TaskQueue because not all tasks should be considered for undo history! Right now just the AddRemoveTagsTask is undoable.
- NylasAPI.makeRequest now returns a promise which resolves with the result or rejects with an error. For things that still need them, there's still `success` and `error` callbacks. I also added `started:(req) ->` which allows you to get the underlying request.
- Aborting a NylasAPI request now makes it call it's error callback / promise reject.
- You can now run code after perform local has completed using this syntax:
```
task = new AddRemoveTagsTask(focused, ['archive'], ['inbox'])
task.waitForPerformLocal().then ->
Actions.setFocus(collection: 'thread', item: nextFocus)
Actions.setCursorPosition(collection: 'thread', item: nextKeyboard)
Actions.queueTask(task)
```
- In specs, you can now use `advanceClock` to get through a Promise.then/catch/finally. Turns out it was using something low level and not using setTimeout(0).
- The TaskQueue uses promises better and defers a lot of the complexity around queueState for performLocal/performRemote to a task subclass called APITask. APITask implements "perform" and breaks it into "performLocal" and "performRemote".
- All tasks either resolve or reject. They're always removed from the queue, unless they resolve with Task.Status.Retry, which means they internally did a .catch (err) => Promise.resolve(Task.Status.Retry) and they want to be run again later.
- API tasks retry until they succeed or receive a NylasAPI.PermanentErrorCode (400,404,500), in which case they revert and finish.
- The AddRemoveTags Task can now take more than one thread! This is super cool because you can undo/redo a bulk action and also because we'll probably have a bulk tag modification API endpoint soon.
Getting undo / redo working revealed that the thread versioning system we built isn't working because the server was incrementing things by more than 1 at a time. Now we count the number of unresolved "optimistic" changes we've made to a given model, and only accept the server's version of it once the number of optimistic changes is back at zero.
Known Issues:
- AddRemoveTagsTasks aren't dependent on each other, so if you (undo/redo x lots) and then come back online, all the tasks try to add / remove all the tags at the same time. To fix this we can either allow the tasks to be merged together into a minimal set or make them block on each other.
- When Offline, you still get errors in the console for GET requests. Need to catch these and display an offline status bar.
- The metadata tasks haven't been updated yet to the new API. Wanted to get it reviewed first!
Test Plan: All the tests still pass!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1694
2015-07-08 01:38:53 +08:00
|
|
|
</div>
|
2015-06-12 02:52:49 +08:00
|
|
|
|
2016-01-27 07:36:13 +08:00
|
|
|
_renderFileAttachments: ->
|
2016-01-28 05:49:34 +08:00
|
|
|
nonImageFiles = @_nonImageFiles(@state.files).map((file) =>
|
2016-01-27 07:36:13 +08:00
|
|
|
@_renderFileAttachment(file, "Attachment")
|
|
|
|
)
|
2016-01-28 05:49:34 +08:00
|
|
|
imageFiles = @_imageFiles(@state.files).map((file) =>
|
2016-01-27 07:36:13 +08:00
|
|
|
@_renderFileAttachment(file, "Attachment:Image")
|
|
|
|
)
|
|
|
|
nonImageFiles.concat(imageFiles)
|
2015-06-12 02:52:49 +08:00
|
|
|
|
2016-01-27 07:36:13 +08:00
|
|
|
_renderFileAttachment: (file, role) ->
|
2015-06-12 02:52:49 +08:00
|
|
|
props =
|
|
|
|
file: file
|
|
|
|
removable: true
|
2016-01-27 07:36:13 +08:00
|
|
|
targetPath: FileDownloadStore.pathForFile(file)
|
2015-08-29 02:12:53 +08:00
|
|
|
messageClientId: @props.draftClientId
|
2015-06-12 02:52:49 +08:00
|
|
|
|
feat(offline-mode, undo-redo): Tasks handle network errors better and retry, undo/redo based on tasks
Summary:
This diff does a couple things:
- Undo redo with a new undo/redo store that maintains it's own queue of undo/redo tasks. This queue is separate from the TaskQueue because not all tasks should be considered for undo history! Right now just the AddRemoveTagsTask is undoable.
- NylasAPI.makeRequest now returns a promise which resolves with the result or rejects with an error. For things that still need them, there's still `success` and `error` callbacks. I also added `started:(req) ->` which allows you to get the underlying request.
- Aborting a NylasAPI request now makes it call it's error callback / promise reject.
- You can now run code after perform local has completed using this syntax:
```
task = new AddRemoveTagsTask(focused, ['archive'], ['inbox'])
task.waitForPerformLocal().then ->
Actions.setFocus(collection: 'thread', item: nextFocus)
Actions.setCursorPosition(collection: 'thread', item: nextKeyboard)
Actions.queueTask(task)
```
- In specs, you can now use `advanceClock` to get through a Promise.then/catch/finally. Turns out it was using something low level and not using setTimeout(0).
- The TaskQueue uses promises better and defers a lot of the complexity around queueState for performLocal/performRemote to a task subclass called APITask. APITask implements "perform" and breaks it into "performLocal" and "performRemote".
- All tasks either resolve or reject. They're always removed from the queue, unless they resolve with Task.Status.Retry, which means they internally did a .catch (err) => Promise.resolve(Task.Status.Retry) and they want to be run again later.
- API tasks retry until they succeed or receive a NylasAPI.PermanentErrorCode (400,404,500), in which case they revert and finish.
- The AddRemoveTags Task can now take more than one thread! This is super cool because you can undo/redo a bulk action and also because we'll probably have a bulk tag modification API endpoint soon.
Getting undo / redo working revealed that the thread versioning system we built isn't working because the server was incrementing things by more than 1 at a time. Now we count the number of unresolved "optimistic" changes we've made to a given model, and only accept the server's version of it once the number of optimistic changes is back at zero.
Known Issues:
- AddRemoveTagsTasks aren't dependent on each other, so if you (undo/redo x lots) and then come back online, all the tasks try to add / remove all the tags at the same time. To fix this we can either allow the tasks to be merged together into a minimal set or make them block on each other.
- When Offline, you still get errors in the console for GET requests. Need to catch these and display an offline status bar.
- The metadata tasks haven't been updated yet to the new API. Wanted to get it reviewed first!
Test Plan: All the tests still pass!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1694
2015-07-08 01:38:53 +08:00
|
|
|
if role is "Attachment"
|
2015-07-16 04:15:05 +08:00
|
|
|
className = "file-wrap"
|
feat(offline-mode, undo-redo): Tasks handle network errors better and retry, undo/redo based on tasks
Summary:
This diff does a couple things:
- Undo redo with a new undo/redo store that maintains it's own queue of undo/redo tasks. This queue is separate from the TaskQueue because not all tasks should be considered for undo history! Right now just the AddRemoveTagsTask is undoable.
- NylasAPI.makeRequest now returns a promise which resolves with the result or rejects with an error. For things that still need them, there's still `success` and `error` callbacks. I also added `started:(req) ->` which allows you to get the underlying request.
- Aborting a NylasAPI request now makes it call it's error callback / promise reject.
- You can now run code after perform local has completed using this syntax:
```
task = new AddRemoveTagsTask(focused, ['archive'], ['inbox'])
task.waitForPerformLocal().then ->
Actions.setFocus(collection: 'thread', item: nextFocus)
Actions.setCursorPosition(collection: 'thread', item: nextKeyboard)
Actions.queueTask(task)
```
- In specs, you can now use `advanceClock` to get through a Promise.then/catch/finally. Turns out it was using something low level and not using setTimeout(0).
- The TaskQueue uses promises better and defers a lot of the complexity around queueState for performLocal/performRemote to a task subclass called APITask. APITask implements "perform" and breaks it into "performLocal" and "performRemote".
- All tasks either resolve or reject. They're always removed from the queue, unless they resolve with Task.Status.Retry, which means they internally did a .catch (err) => Promise.resolve(Task.Status.Retry) and they want to be run again later.
- API tasks retry until they succeed or receive a NylasAPI.PermanentErrorCode (400,404,500), in which case they revert and finish.
- The AddRemoveTags Task can now take more than one thread! This is super cool because you can undo/redo a bulk action and also because we'll probably have a bulk tag modification API endpoint soon.
Getting undo / redo working revealed that the thread versioning system we built isn't working because the server was incrementing things by more than 1 at a time. Now we count the number of unresolved "optimistic" changes we've made to a given model, and only accept the server's version of it once the number of optimistic changes is back at zero.
Known Issues:
- AddRemoveTagsTasks aren't dependent on each other, so if you (undo/redo x lots) and then come back online, all the tasks try to add / remove all the tags at the same time. To fix this we can either allow the tasks to be merged together into a minimal set or make them block on each other.
- When Offline, you still get errors in the console for GET requests. Need to catch these and display an offline status bar.
- The metadata tasks haven't been updated yet to the new API. Wanted to get it reviewed first!
Test Plan: All the tests still pass!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1694
2015-07-08 01:38:53 +08:00
|
|
|
else
|
2015-07-16 04:15:05 +08:00
|
|
|
className = "file-wrap file-image-wrap"
|
2015-06-12 02:52:49 +08:00
|
|
|
|
|
|
|
<InjectedComponent key={file.id}
|
|
|
|
matching={role: role}
|
|
|
|
className={className}
|
|
|
|
exposedProps={props} />
|
|
|
|
|
2016-01-27 07:36:13 +08:00
|
|
|
_renderUploadAttachments: ->
|
|
|
|
nonImageUploads = @_nonImageFiles(@state.uploads).map((upload) ->
|
|
|
|
<FileUpload key={upload.id} upload={upload} />
|
|
|
|
)
|
|
|
|
imageUploads = @_imageFiles(@state.uploads).map((upload) ->
|
2016-01-27 08:57:03 +08:00
|
|
|
<ImageFileUpload key={upload.id} upload={upload} />
|
2016-01-27 07:36:13 +08:00
|
|
|
)
|
|
|
|
nonImageUploads.concat(imageUploads)
|
2015-06-12 02:52:49 +08:00
|
|
|
|
2016-01-27 07:36:13 +08:00
|
|
|
_imageFiles: (files) ->
|
|
|
|
_.filter(files, Utils.shouldDisplayAsImage)
|
2015-06-26 22:42:41 +08:00
|
|
|
|
2016-01-27 07:36:13 +08:00
|
|
|
_nonImageFiles: (files) ->
|
|
|
|
_.reject(files, Utils.shouldDisplayAsImage)
|
2015-06-12 02:52:49 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_renderActionsRegion: =>
|
2015-08-29 02:12:53 +08:00
|
|
|
return <div></div> unless @props.draftClientId
|
2015-03-18 07:19:40 +08:00
|
|
|
|
2015-04-25 02:33:10 +08:00
|
|
|
<InjectedComponentSet className="composer-action-bar-content"
|
feat(unsafe-components): Wrap injected components, catch exceptions, clean up ComponentRegistry
Summary:
This diff gives the ComponentRegistry a cleaner, smaller API. Instead of querying by name, location or role,
it's now just location and role, and you can register components for one or more location and one or more
roles without assigning the entries in the registry separate names.
When you register with the ComponentRegistry, the syntax is also cleaner and uses the component's displayName
instead of requiring you to provide a name. You also provide the actual component when unregistering, ensuring
that you can't unregister someone else's component.
InjectedComponent and InjectedComponentSet now wrap their children in UnsafeComponent, which prevents
render/component lifecycle problems from propogating.
Existing components have been updated:
1. maxWidth / minWidth are now containerStyles.maxWidth/minWidth
2. displayName is now required to use the CR.
3. containerRequired = false can be provided to exempt a component from being wrapped in an UnsafeComponent.
This is useful because it's slightly faster and keeps DOM flat.
This diff also makes the "Show Component Regions" more awesome. It displays column regions, since they now
use the InjectedComponentSet, and also shows for InjectedComponent as well as InjectedComponentSet.
Change ComponentRegistry syntax, lots more work on safely wrapping items. See description.
Fix for inline flexbox scenarios (message actions)
Allow ~/.inbox/packages to be symlinked to a github repo
Test Plan: Run tests!
Reviewers: evan
Reviewed By: evan
Differential Revision: https://review.inboxapp.com/D1457
2015-05-01 07:10:15 +08:00
|
|
|
matching={role: "Composer:ActionButton"}
|
2015-08-29 02:12:53 +08:00
|
|
|
exposedProps={draftClientId:@props.draftClientId, threadId: @props.threadId}>
|
2015-03-18 07:19:40 +08:00
|
|
|
|
2015-04-25 02:33:10 +08:00
|
|
|
<button className="btn btn-toolbar btn-trash" style={order: 100}
|
2015-10-22 02:03:27 +08:00
|
|
|
title="Delete draft"
|
2015-06-18 07:03:50 +08:00
|
|
|
onClick={@_destroyDraft}><RetinaImg name="icon-composer-trash.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
|
2015-04-22 09:16:08 +08:00
|
|
|
|
2015-04-25 02:33:10 +08:00
|
|
|
<button className="btn btn-toolbar btn-attach" style={order: 50}
|
2015-10-22 02:03:27 +08:00
|
|
|
title="Attach file"
|
2016-01-27 10:48:21 +08:00
|
|
|
onClick={@_selectAttachment}><RetinaImg name="icon-composer-attachment.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
|
2015-04-22 09:16:08 +08:00
|
|
|
|
2015-04-25 02:33:10 +08:00
|
|
|
<div style={order: 0, flex: 1} />
|
|
|
|
|
2016-01-26 06:14:09 +08:00
|
|
|
<SendActionButton draft={@_proxy?.draft()}
|
|
|
|
ref="sendActionButton"
|
|
|
|
isValidDraft={@_isValidDraft} />
|
2015-04-25 02:33:10 +08:00
|
|
|
|
|
|
|
</InjectedComponentSet>
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
isForwardedMessage: =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return false if not @_proxy
|
2015-03-11 09:44:33 +08:00
|
|
|
draft = @_proxy.draft()
|
2015-03-26 03:41:48 +08:00
|
|
|
Utils.isForwardedMessage(draft)
|
2015-03-11 09:44:33 +08:00
|
|
|
|
2015-06-18 07:03:50 +08:00
|
|
|
# This lets us click outside of the `contenteditable`'s `contentBody`
|
2015-06-24 06:21:25 +08:00
|
|
|
# and simulate what happens when you click beneath the text *in* the
|
|
|
|
# contentEditable.
|
2015-11-19 04:09:07 +08:00
|
|
|
#
|
|
|
|
# Unfortunately, we need to manually keep track of the "click" in
|
|
|
|
# separate mouseDown, mouseUp events because we need to ensure that the
|
|
|
|
# start and end target are both not in the contenteditable. This ensures
|
|
|
|
# that this behavior doesn't interfear with a click and drag selection.
|
|
|
|
_onMouseDownComposerBody: (event) =>
|
|
|
|
if React.findDOMNode(@refs[Fields.Body]).contains(event.target)
|
|
|
|
@_mouseDownTarget = null
|
|
|
|
else @_mouseDownTarget = event.target
|
|
|
|
|
|
|
|
_onMouseUpComposerBody: (event) =>
|
|
|
|
if event.target is @_mouseDownTarget
|
2016-01-13 04:32:59 +08:00
|
|
|
# We don't set state directly here because we want the native
|
|
|
|
# contenteditable focus behavior. When the contenteditable gets focused
|
|
|
|
# the focused field state will be properly set via editor.onFocus
|
|
|
|
@refs[Fields.Body].nativeFocus()
|
2015-11-19 04:09:07 +08:00
|
|
|
@_mouseDownTarget = null
|
|
|
|
|
2015-12-11 05:51:44 +08:00
|
|
|
# When a user focuses the composer, it's possible that no input is
|
|
|
|
# initially focused. If this happens, we focus the contenteditable. If
|
|
|
|
# we didn't focus the contenteditable, the user may start typing and
|
|
|
|
# erroneously trigger keyboard shortcuts.
|
|
|
|
_onFocusIn: (event) =>
|
|
|
|
return if DOMUtils.closest(event.target, DOMUtils.inputTypes())
|
2016-01-06 03:34:26 +08:00
|
|
|
@setState(focusedField: @_initiallyFocusedField(@_proxy.draft()))
|
2015-12-11 05:51:44 +08:00
|
|
|
|
2015-11-19 04:09:07 +08:00
|
|
|
_onMouseMoveComposeBody: (event) =>
|
|
|
|
if @_mouseComposeBody is "down" then @_mouseComposeBody = "move"
|
2015-06-18 07:03:50 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_onDraftChanged: =>
|
2015-07-16 22:41:04 +08:00
|
|
|
return if @_ignoreNextTrigger
|
2015-05-16 01:45:18 +08:00
|
|
|
return unless @_proxy
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
draft = @_proxy.draft()
|
2015-06-16 05:48:37 +08:00
|
|
|
|
2015-03-03 07:33:58 +08:00
|
|
|
if not @_initialHistorySave
|
|
|
|
@_saveToHistory()
|
|
|
|
@_initialHistorySave = true
|
2015-06-16 05:48:37 +08:00
|
|
|
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
state =
|
|
|
|
to: draft.to
|
|
|
|
cc: draft.cc
|
|
|
|
bcc: draft.bcc
|
feat(accounts): Kill namespaces, long live accounts
Summary:
This diff replaces the Namespace object with the Account object, and changes all references to namespace_id => account_id, etc. The endpoints are now `/threads` instead of `/n/<id>/threads`.
This diff also adds preliminary support for multiple accounts. When you log in, we now log you in to all the attached accounts on edgehill server. From the preferences panel, you can auth with / unlink additional accounts. Shockingly, this all seems to pretty much work.
When replying to a thread, you cannot switch from addresses. However, when creating a new message in a popout composer, you can change the from address and the SaveDraftTask will delete/re-root the draft on the new account.
Search bar doesn't need to do full refresh on clear if it never committed
Allow drafts to be switched to a different account when not in reply to an existing thread
Fix edge case where ChangeMailTask throws exception if no models are modified during performLocal
Show many dots for many accounts in long polling status bar
add/remove accounts from prefs
Spec fixes!
Test Plan: Run tests, none broken!
Reviewers: evan, dillon
Reviewed By: evan, dillon
Differential Revision: https://phab.nylas.com/D1928
2015-08-22 06:29:58 +08:00
|
|
|
from: draft.from
|
2015-09-14 22:37:00 +08:00
|
|
|
body: draft.body
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
files: draft.files
|
2016-01-28 16:33:22 +08:00
|
|
|
uploads: draft.uploads
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
subject: draft.subject
|
2016-01-27 05:54:31 +08:00
|
|
|
accounts: @_getAccountsForSend()
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
|
|
|
if !@state.populated
|
|
|
|
_.extend state,
|
|
|
|
populated: true
|
2015-09-14 22:37:00 +08:00
|
|
|
focusedField: @_initiallyFocusedField(draft)
|
|
|
|
enabledFields: @_initiallyEnabledFields(draft)
|
|
|
|
showQuotedText: @isForwardedMessage()
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
state = @_verifyEnabledFields(draft, state)
|
2015-07-14 07:30:02 +08:00
|
|
|
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
@setState(state)
|
2015-03-11 09:27:10 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
_initiallyFocusedField: (draft) ->
|
|
|
|
return Fields.To if draft.to.length is 0
|
|
|
|
return Fields.Subject if (draft.subject ? "").trim().length is 0
|
2015-12-30 01:15:18 +08:00
|
|
|
|
2016-01-06 03:34:26 +08:00
|
|
|
shouldFocusBody = @props.mode isnt 'inline' or draft.pristine or
|
2015-12-30 01:15:18 +08:00
|
|
|
(FocusedContentStore.didFocusUsingClick('thread') is true)
|
|
|
|
return Fields.Body if shouldFocusBody
|
2016-01-06 03:34:26 +08:00
|
|
|
return null
|
2015-09-14 22:37:00 +08:00
|
|
|
|
|
|
|
_verifyEnabledFields: (draft, state) ->
|
|
|
|
enabledFields = @state.enabledFields.concat(state.enabledFields)
|
|
|
|
updated = false
|
|
|
|
if draft.cc.length > 0
|
|
|
|
updated = true
|
|
|
|
enabledFields.push(Fields.Cc)
|
|
|
|
|
|
|
|
if draft.bcc.length > 0
|
|
|
|
updated = true
|
|
|
|
enabledFields.push(Fields.Bcc)
|
|
|
|
|
|
|
|
if updated
|
|
|
|
state.enabledFields = _.uniq(enabledFields)
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
_initiallyEnabledFields: (draft) ->
|
|
|
|
enabledFields = [Fields.To]
|
|
|
|
enabledFields.push Fields.Cc if not _.isEmpty(draft.cc)
|
|
|
|
enabledFields.push Fields.Bcc if not _.isEmpty(draft.bcc)
|
|
|
|
enabledFields.push Fields.From if @_shouldShowFromField(draft)
|
|
|
|
enabledFields.push Fields.Subject if @_shouldEnableSubject()
|
|
|
|
enabledFields.push Fields.Body
|
|
|
|
return enabledFields
|
|
|
|
|
2016-01-27 05:54:31 +08:00
|
|
|
_getAccountsForSend: =>
|
2016-01-27 10:03:01 +08:00
|
|
|
if @_proxy.draft()?.replyToMessageId
|
2016-01-26 10:20:19 +08:00
|
|
|
[AccountStore.accountForId(@_proxy.draft().accountId)]
|
|
|
|
else
|
|
|
|
AccountStore.accounts()
|
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
# When the account store changes, the From field may or may not still
|
|
|
|
# be in scope. We need to make sure to update our enabled fields.
|
|
|
|
_onAccountStoreChanged: =>
|
2016-01-27 05:54:31 +08:00
|
|
|
accounts = @_getAccountsForSend()
|
2015-12-10 04:35:40 +08:00
|
|
|
enabledFields = if @_shouldShowFromField(@_proxy?.draft())
|
|
|
|
@state.enabledFields.concat [Fields.From]
|
2015-09-14 22:37:00 +08:00
|
|
|
else
|
2015-12-10 04:35:40 +08:00
|
|
|
_.without(@state.enabledFields, Fields.From)
|
2016-01-26 10:20:19 +08:00
|
|
|
@setState {enabledFields, accounts}
|
2015-09-14 22:37:00 +08:00
|
|
|
|
2015-12-12 11:04:14 +08:00
|
|
|
_shouldShowFromField: (draft) =>
|
2016-01-26 10:20:19 +08:00
|
|
|
return true if draft
|
|
|
|
return false
|
2015-09-14 22:37:00 +08:00
|
|
|
|
|
|
|
_shouldEnableSubject: =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return false unless @_proxy
|
2015-03-11 09:27:10 +08:00
|
|
|
draft = @_proxy.draft()
|
|
|
|
if _.isEmpty(draft.subject ? "") then return true
|
2015-03-11 09:44:33 +08:00
|
|
|
else if @isForwardedMessage() then return true
|
2015-09-09 01:27:50 +08:00
|
|
|
else if draft.replyToMessageId then return false
|
|
|
|
else return true
|
2015-03-11 09:27:10 +08:00
|
|
|
|
2015-07-24 02:10:51 +08:00
|
|
|
_shouldAcceptDrop: (event) =>
|
2015-07-18 07:36:28 +08:00
|
|
|
# Ensure that you can't pick up a file and drop it on the same draft
|
|
|
|
nonNativeFilePath = @_nonNativeFilePathForDrop(event)
|
|
|
|
|
|
|
|
hasNativeFile = event.dataTransfer.files.length > 0
|
|
|
|
hasNonNativeFilePath = nonNativeFilePath isnt null
|
|
|
|
|
|
|
|
return hasNativeFile or hasNonNativeFilePath
|
|
|
|
|
2015-07-24 02:10:51 +08:00
|
|
|
_nonNativeFilePathForDrop: (event) =>
|
2015-07-18 07:36:28 +08:00
|
|
|
if "text/nylas-file-url" in event.dataTransfer.types
|
|
|
|
downloadURL = event.dataTransfer.getData("text/nylas-file-url")
|
|
|
|
downloadFilePath = downloadURL.split('file://')[1]
|
|
|
|
if downloadFilePath
|
|
|
|
return downloadFilePath
|
|
|
|
|
|
|
|
# Accept drops of images from within the app
|
|
|
|
if "text/uri-list" in event.dataTransfer.types
|
|
|
|
uri = event.dataTransfer.getData('text/uri-list')
|
|
|
|
if uri.indexOf('file://') is 0
|
|
|
|
uri = decodeURI(uri.split('file://')[1])
|
|
|
|
return uri
|
|
|
|
|
|
|
|
return null
|
2015-07-16 11:06:11 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_onDrop: (e) =>
|
2015-07-16 04:15:05 +08:00
|
|
|
# Accept drops of real files from other applications
|
2015-03-11 02:23:31 +08:00
|
|
|
for file in e.dataTransfer.files
|
2016-01-27 10:48:21 +08:00
|
|
|
Actions.addAttachment({filePath: file.path, messageClientId: @props.draftClientId})
|
2015-07-16 04:15:05 +08:00
|
|
|
|
2015-07-18 07:36:28 +08:00
|
|
|
# Accept drops from attachment components / images within the app
|
|
|
|
if (uri = @_nonNativeFilePathForDrop(e))
|
2016-01-27 10:48:21 +08:00
|
|
|
Actions.addAttachment({filePath: uri, messageClientId: @props.draftClientId})
|
2015-07-16 04:15:05 +08:00
|
|
|
|
2015-06-09 03:41:31 +08:00
|
|
|
_onFilePaste: (path) =>
|
2016-01-27 10:48:21 +08:00
|
|
|
Actions.addAttachment({filePath: path, messageClientId: @props.draftClientId})
|
2015-06-09 03:41:31 +08:00
|
|
|
|
2015-08-06 08:38:44 +08:00
|
|
|
_onChangeParticipants: (changes={}) =>
|
|
|
|
@_addToProxy(changes)
|
|
|
|
|
|
|
|
_onChangeSubject: (event) =>
|
|
|
|
@_addToProxy(subject: event.target.value)
|
2015-03-28 07:35:27 +08:00
|
|
|
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
_onBodyChanged: (event) =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return unless @_proxy
|
2015-07-16 22:41:04 +08:00
|
|
|
|
2015-10-31 08:03:33 +08:00
|
|
|
newBody = @_showQuotedText(event.target.value)
|
|
|
|
|
2015-07-16 22:41:04 +08:00
|
|
|
# The body changes extremely frequently (on every key stroke). To keep
|
|
|
|
# performance up, we don't want to trigger every single key stroke
|
|
|
|
# since that will cause an entire composer re-render. We, however,
|
|
|
|
# never want to lose any data, so we still add data to the proxy on
|
|
|
|
# every keystroke.
|
|
|
|
#
|
|
|
|
# We want to use debounce instead of throttle because we don't want ot
|
|
|
|
# trigger janky re-renders mid quick-type. Let's just do it at the end
|
|
|
|
# when you're done typing and about to move onto something else.
|
2015-10-31 08:03:33 +08:00
|
|
|
@_addToProxy({body: newBody}, {fromBodyChange: true})
|
2015-07-16 22:41:04 +08:00
|
|
|
@_throttledTrigger ?= _.debounce =>
|
|
|
|
@_ignoreNextTrigger = false
|
|
|
|
@_proxy.trigger()
|
|
|
|
, 100
|
|
|
|
|
|
|
|
@_throttledTrigger()
|
|
|
|
return
|
2015-03-03 07:33:58 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_addToProxy: (changes={}, source={}) =>
|
2015-03-24 07:33:28 +08:00
|
|
|
return unless @_proxy
|
|
|
|
|
2015-03-03 07:33:58 +08:00
|
|
|
selections = @_getSelections()
|
|
|
|
|
|
|
|
oldDraft = @_proxy.draft()
|
2015-03-24 07:33:28 +08:00
|
|
|
return if _.all changes, (change, key) -> _.isEqual(change, oldDraft[key])
|
2015-07-16 22:41:04 +08:00
|
|
|
|
|
|
|
# Other extensions might want to hear about changes immediately. We
|
|
|
|
# only need to prevent this view from re-rendering until we're done
|
|
|
|
# throttling body changes.
|
|
|
|
if source.fromBodyChange then @_ignoreNextTrigger = true
|
|
|
|
|
2015-03-03 07:33:58 +08:00
|
|
|
@_proxy.changes.add(changes)
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-03-03 07:33:58 +08:00
|
|
|
@_saveToHistory(selections) unless source.fromUndoManager
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2016-01-26 06:14:09 +08:00
|
|
|
_isValidDraft: (options = {}) =>
|
|
|
|
return false unless @_proxy
|
2015-06-01 23:24:32 +08:00
|
|
|
|
2015-08-04 08:19:07 +08:00
|
|
|
# We need to check the `DraftStore` because the `DraftStore` is
|
|
|
|
# immediately and synchronously updated as soon as this function
|
|
|
|
# fires. Since `setState` is asynchronous, if we used that as our only
|
|
|
|
# check, then we might get a false reading.
|
2016-01-26 06:14:09 +08:00
|
|
|
return false if DraftStore.isSendingDraft(@props.draftClientId)
|
2015-06-01 23:24:32 +08:00
|
|
|
|
2015-02-07 06:46:30 +08:00
|
|
|
draft = @_proxy.draft()
|
|
|
|
remote = require('remote')
|
|
|
|
dialog = remote.require('dialog')
|
|
|
|
|
2015-08-04 04:06:28 +08:00
|
|
|
allRecipients = [].concat(draft.to, draft.cc, draft.bcc)
|
|
|
|
for contact in allRecipients
|
|
|
|
if not ContactStore.isValidContact(contact)
|
|
|
|
dealbreaker = "#{contact.email} is not a valid email address - please remove or edit it before sending."
|
|
|
|
if allRecipients.length is 0
|
|
|
|
dealbreaker = 'You need to provide one or more recipients before sending the message.'
|
|
|
|
|
|
|
|
if dealbreaker
|
2015-02-07 06:46:30 +08:00
|
|
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
|
|
type: 'warning',
|
|
|
|
buttons: ['Edit Message'],
|
|
|
|
message: 'Cannot Send',
|
2015-08-04 04:06:28 +08:00
|
|
|
detail: dealbreaker
|
2015-02-07 06:46:30 +08:00
|
|
|
})
|
2016-01-26 06:14:09 +08:00
|
|
|
return false
|
2015-02-07 06:46:30 +08:00
|
|
|
|
feat(signatures): Initial signature support
Summary:
- Draft Store extensions can now implement `prepareNewDraft` to have an opportunity to change a draft before it's displayed for the first time.
- When composers are torn down, they delete their draft if it is still pristine. This makes the behavior of closing unedited popout drafts the same as leaving unedited inline drafts.
- The DraftStoreProxy keeps the initial body of the draft *if* it started in a pristine state. This means "is the body empty" is just a simple == check, and it takes into account anything added to the body by extensions.
- Calling Actions.destroyDraft doesn't blow up anymore if the draft session can't be found. This was a bug and meant that you couldn't destroy drafts which hadn't been previously edited, and also meant that bad things(tm) happened when you called destroyDraft twice, which seemed like overkill.
- DestroyDraft task now exits gracefully when the draft cannot be found.
You can test this feature by adding the following to your config.cson:
```
signatures:
NAMESPACEID: "<br/><br/><div id=\"Signature\"><div id=\"divtagdefaultwrapper\" style=\"font-size:12pt; color:#000000; background-color:#FFFFFF; font-family:Calibri,Arial,Helvetica,sans-serif\"><p></p><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"450\" style=\"font-family:'Times New Roman'; table-layout:fixed\"><tbody><tr><td class=\"logo-td\" align=\"left\" valign=\"top\" width=\"76\"><p style=\"margin-bottom:10px; margin-right:10px; font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px\"><a href=\"http://www.nylas.com/\" class=\"clink logo-container\" style=\"text-decoration:none\"><img alt=\"Nylas\" border=\"0\" class=\"sig-logo\" height=\"80\" width=\"66\" style=\"-webkit-user-select: none;\" src=\"https://s3-us-west-2.amazonaws.com/nylas-static-assets/nylas-email-signature.png\"></a></p><p class=\"social-list\" style=\"font-size:0px; line-height:0; font-family:Helvetica,Arial,sans-serif\"></p></td><td align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"spacer-td\" width=\"16\" style=\"border-left-width:2px; border-left-style:solid; border-left-color:rgb(30,162,162)\"><img width=\"10\" style=\"-webkit-user-select: none;\" src=\"https://s3.amazonaws.com/htmlsig-assets/spacer.gif\"></td><td align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"content-td\" width=\"368\"><div class=\"content-pad\"><p style=\"font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px; color:rgb(33,33,33); margin-bottom:10px\"><span class=\"txt signature_name-target sig-hide\" style=\"font-weight:bold; display:inline\">Gleb Polyakov</span> <span class=\"email-sep break\" style=\"display:inline\"><br></span><a class=\"link email signature_email-target sig-hide\" href=\"mailto:gleb@nylas.com\" style=\"color:rgb(30,162,162); text-decoration:none; display:inline\">gleb@nylas.com</a><span class=\"signature_email-sep sep\" style=\"display:inline\"> / </span><span class=\"txt signature_mobilephone-target sig-hide\" style=\"display:inline\">404-786-4100</span></p><p style=\"font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px; margin-bottom:10px\"><span class=\"txt signature_companyname-target sig-hide\" style=\"font-weight:bold; color:rgb(33,33,33); display:inline\">Nylas</span> <span class=\"company-sep break\" style=\"display:inline\"><br></span><span class=\"address-sep break\"></span><span class=\"address2-sep break\"></span><span class=\"website-sep break\"></span><a class=\"link signature_website-target sig-hide\" href=\"http://www.nylas.com/\" style=\"color:rgb(30,162,162); text-decoration:none; display:inline\">http://www.nylas.com</a></p></div></td></tr><tr><td colspan=\"3\"></td></tr><tr><td colspan=\"3\"></td></tr><tr><td colspan=\"3\"><p class=\"txt signature_disclaimer-target\" style=\"font-family:Helvetica,Arial,sans-serif; color:rgb(33,33,33); font-size:9px; line-height:12px; margin-top:10px\"></p></td></tr></tbody></table><p></p></div></div>"
```
specs for draft store extension hooks, some draft store refactoring
Test Plan: Run a few new specs that make sure extensions are run
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1741
2015-07-15 03:20:06 +08:00
|
|
|
bodyIsEmpty = draft.body is @_proxy.draftPristineBody()
|
2015-06-18 03:29:21 +08:00
|
|
|
forwarded = Utils.isForwardedMessage(draft)
|
|
|
|
hasAttachment = (draft.files ? []).length > 0
|
|
|
|
|
2015-02-07 06:46:30 +08:00
|
|
|
warnings = []
|
2015-06-18 03:29:21 +08:00
|
|
|
|
2015-02-07 06:46:30 +08:00
|
|
|
if draft.subject.length is 0
|
|
|
|
warnings.push('without a subject line')
|
2015-06-18 03:29:21 +08:00
|
|
|
|
|
|
|
if @_mentionsAttachment(draft.body) and not hasAttachment
|
2015-02-07 06:46:30 +08:00
|
|
|
warnings.push('without an attachment')
|
|
|
|
|
2015-06-18 03:29:21 +08:00
|
|
|
if bodyIsEmpty and not forwarded and not hasAttachment
|
2015-05-16 01:53:22 +08:00
|
|
|
warnings.push('without a body')
|
|
|
|
|
feat(editor-region): Add support to register components as editors
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
|
|
|
# Check third party warnings added via Composer extensions
|
|
|
|
for extension in ExtensionRegistry.Composer.extensions()
|
2015-03-21 01:23:50 +08:00
|
|
|
continue unless extension.warningsForSending
|
2015-12-31 00:36:47 +08:00
|
|
|
warnings = warnings.concat(extension.warningsForSending({draft}))
|
2015-03-21 01:23:50 +08:00
|
|
|
|
2015-02-07 06:46:30 +08:00
|
|
|
if warnings.length > 0 and not options.force
|
2015-11-26 06:13:21 +08:00
|
|
|
response = dialog.showMessageBox(remote.getCurrentWindow(), {
|
2015-02-07 06:46:30 +08:00
|
|
|
type: 'warning',
|
2015-08-11 03:19:40 +08:00
|
|
|
buttons: ['Send Anyway', 'Cancel'],
|
2015-02-07 06:46:30 +08:00
|
|
|
message: 'Are you sure?',
|
|
|
|
detail: "Send #{warnings.join(' and ')}?"
|
2015-11-26 06:13:21 +08:00
|
|
|
})
|
|
|
|
if response is 0 # response is button array index
|
2016-01-26 06:14:09 +08:00
|
|
|
return @_isValidDraft({force: true})
|
|
|
|
else return false
|
|
|
|
|
|
|
|
return true
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2016-01-26 06:14:09 +08:00
|
|
|
_onPrimarySend: ->
|
|
|
|
@refs["sendActionButton"].primaryClick()
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2015-06-18 03:29:21 +08:00
|
|
|
_mentionsAttachment: (body) =>
|
2015-12-08 07:34:03 +08:00
|
|
|
body = QuotedHTMLTransformer.removeQuotedHTML(body.toLowerCase().trim())
|
2016-01-13 10:44:37 +08:00
|
|
|
signatureIndex = body.indexOf('<div class="nylas-n1-signature">')
|
|
|
|
body = body[...signatureIndex] if signatureIndex isnt -1
|
2015-07-09 00:51:33 +08:00
|
|
|
return body.indexOf("attach") >= 0
|
2015-03-14 03:55:52 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_destroyDraft: =>
|
2015-08-29 02:12:53 +08:00
|
|
|
Actions.destroyDraft(@props.draftClientId)
|
fix(drafts): Various improvements and fixes to drafts, draft state management
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
2015-02-04 08:24:31 +08:00
|
|
|
|
2016-01-27 10:48:21 +08:00
|
|
|
_selectAttachment: =>
|
|
|
|
Actions.selectAttachment({messageClientId: @props.draftClientId})
|
2015-02-11 03:10:14 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
undo: (event) =>
|
2015-03-03 07:33:58 +08:00
|
|
|
event.preventDefault()
|
|
|
|
event.stopPropagation()
|
|
|
|
historyItem = @undoManager.undo() ? {}
|
|
|
|
return unless historyItem.state?
|
|
|
|
|
|
|
|
@_recoveredSelection = historyItem.currentSelection
|
|
|
|
@_addToProxy historyItem.state, fromUndoManager: true
|
2015-05-20 07:12:39 +08:00
|
|
|
@_recoveredSelection = null
|
2015-03-03 07:33:58 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
redo: (event) =>
|
2015-03-03 07:33:58 +08:00
|
|
|
event.preventDefault()
|
|
|
|
event.stopPropagation()
|
|
|
|
historyItem = @undoManager.redo() ? {}
|
|
|
|
return unless historyItem.state?
|
|
|
|
|
|
|
|
@_recoveredSelection = historyItem.currentSelection
|
|
|
|
@_addToProxy historyItem.state, fromUndoManager: true
|
2015-05-20 07:12:39 +08:00
|
|
|
@_recoveredSelection = null
|
2015-03-03 07:33:58 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_getSelections: =>
|
2015-09-14 22:37:00 +08:00
|
|
|
currentSelection: @refs[Fields.Body]?.getCurrentSelection?()
|
|
|
|
previousSelection: @refs[Fields.Body]?.getPreviousSelection?()
|
2015-03-03 07:33:58 +08:00
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_saveToHistory: (selections) =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return unless @_proxy
|
2015-03-03 07:33:58 +08:00
|
|
|
selections ?= @_getSelections()
|
|
|
|
|
|
|
|
newDraft = @_proxy.draft()
|
|
|
|
|
|
|
|
historyItem =
|
|
|
|
previousSelection: selections.previousSelection
|
|
|
|
currentSelection: selections.currentSelection
|
|
|
|
state:
|
|
|
|
body: _.clone newDraft.body
|
|
|
|
subject: _.clone newDraft.subject
|
|
|
|
to: _.clone newDraft.to
|
|
|
|
cc: _.clone newDraft.cc
|
|
|
|
bcc: _.clone newDraft.bcc
|
|
|
|
|
|
|
|
lastState = @undoManager.current()
|
|
|
|
if lastState?
|
|
|
|
lastState.currentSelection = historyItem.previousSelection
|
|
|
|
|
|
|
|
@undoManager.saveToHistory(historyItem)
|
2015-03-26 02:18:07 +08:00
|
|
|
|
feat(signatures): Initial signature support
Summary:
- Draft Store extensions can now implement `prepareNewDraft` to have an opportunity to change a draft before it's displayed for the first time.
- When composers are torn down, they delete their draft if it is still pristine. This makes the behavior of closing unedited popout drafts the same as leaving unedited inline drafts.
- The DraftStoreProxy keeps the initial body of the draft *if* it started in a pristine state. This means "is the body empty" is just a simple == check, and it takes into account anything added to the body by extensions.
- Calling Actions.destroyDraft doesn't blow up anymore if the draft session can't be found. This was a bug and meant that you couldn't destroy drafts which hadn't been previously edited, and also meant that bad things(tm) happened when you called destroyDraft twice, which seemed like overkill.
- DestroyDraft task now exits gracefully when the draft cannot be found.
You can test this feature by adding the following to your config.cson:
```
signatures:
NAMESPACEID: "<br/><br/><div id=\"Signature\"><div id=\"divtagdefaultwrapper\" style=\"font-size:12pt; color:#000000; background-color:#FFFFFF; font-family:Calibri,Arial,Helvetica,sans-serif\"><p></p><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"450\" style=\"font-family:'Times New Roman'; table-layout:fixed\"><tbody><tr><td class=\"logo-td\" align=\"left\" valign=\"top\" width=\"76\"><p style=\"margin-bottom:10px; margin-right:10px; font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px\"><a href=\"http://www.nylas.com/\" class=\"clink logo-container\" style=\"text-decoration:none\"><img alt=\"Nylas\" border=\"0\" class=\"sig-logo\" height=\"80\" width=\"66\" style=\"-webkit-user-select: none;\" src=\"https://s3-us-west-2.amazonaws.com/nylas-static-assets/nylas-email-signature.png\"></a></p><p class=\"social-list\" style=\"font-size:0px; line-height:0; font-family:Helvetica,Arial,sans-serif\"></p></td><td align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"spacer-td\" width=\"16\" style=\"border-left-width:2px; border-left-style:solid; border-left-color:rgb(30,162,162)\"><img width=\"10\" style=\"-webkit-user-select: none;\" src=\"https://s3.amazonaws.com/htmlsig-assets/spacer.gif\"></td><td align=\"left\" valign=\"top\" nowrap=\"nowrap\" class=\"content-td\" width=\"368\"><div class=\"content-pad\"><p style=\"font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px; color:rgb(33,33,33); margin-bottom:10px\"><span class=\"txt signature_name-target sig-hide\" style=\"font-weight:bold; display:inline\">Gleb Polyakov</span> <span class=\"email-sep break\" style=\"display:inline\"><br></span><a class=\"link email signature_email-target sig-hide\" href=\"mailto:gleb@nylas.com\" style=\"color:rgb(30,162,162); text-decoration:none; display:inline\">gleb@nylas.com</a><span class=\"signature_email-sep sep\" style=\"display:inline\"> / </span><span class=\"txt signature_mobilephone-target sig-hide\" style=\"display:inline\">404-786-4100</span></p><p style=\"font-family:Helvetica,Arial,sans-serif; font-size:14px; line-height:16px; margin-bottom:10px\"><span class=\"txt signature_companyname-target sig-hide\" style=\"font-weight:bold; color:rgb(33,33,33); display:inline\">Nylas</span> <span class=\"company-sep break\" style=\"display:inline\"><br></span><span class=\"address-sep break\"></span><span class=\"address2-sep break\"></span><span class=\"website-sep break\"></span><a class=\"link signature_website-target sig-hide\" href=\"http://www.nylas.com/\" style=\"color:rgb(30,162,162); text-decoration:none; display:inline\">http://www.nylas.com</a></p></div></td></tr><tr><td colspan=\"3\"></td></tr><tr><td colspan=\"3\"></td></tr><tr><td colspan=\"3\"><p class=\"txt signature_disclaimer-target\" style=\"font-family:Helvetica,Arial,sans-serif; color:rgb(33,33,33); font-size:9px; line-height:12px; margin-top:10px\"></p></td></tr></tbody></table><p></p></div></div>"
```
specs for draft store extension hooks, some draft store refactoring
Test Plan: Run a few new specs that make sure extensions are run
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1741
2015-07-15 03:20:06 +08:00
|
|
|
_deleteDraftIfEmpty: =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return unless @_proxy
|
2015-08-29 02:12:53 +08:00
|
|
|
if @_proxy.draft().pristine then Actions.destroyDraft(@props.draftClientId)
|
2015-05-01 04:08:29 +08:00
|
|
|
|
|
|
|
module.exports = ComposerView
|