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-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-07-09 00:51:33 +08:00
|
|
|
QuotedHTMLParser,
|
2015-06-12 02:52:49 +08:00
|
|
|
FileDownloadStore} = 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-10-30 05:20:41 +08:00
|
|
|
Contenteditable,
|
2015-04-25 02:33:10 +08:00
|
|
|
InjectedComponent,
|
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
|
|
|
FocusTrackingRegion,
|
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
|
|
|
|
|
|
|
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-08-29 02:12:53 +08:00
|
|
|
draftClientId: React.PropTypes.string.isRequired
|
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.
|
|
|
|
onRequestScrollTo: React.PropTypes.func
|
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
constructor: (@props) ->
|
|
|
|
@state =
|
|
|
|
populated: false
|
|
|
|
to: []
|
|
|
|
cc: []
|
|
|
|
bcc: []
|
|
|
|
body: ""
|
2015-06-12 02:52:49 +08:00
|
|
|
files: []
|
2015-05-01 04:08:29 +08:00
|
|
|
subject: ""
|
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
|
2015-08-29 02:12:53 +08:00
|
|
|
uploads: FileUploadStore.uploadsForMessage(@props.draftClientId) ? []
|
2015-05-01 04:08:29 +08:00
|
|
|
|
|
|
|
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 FileUploadStore.listen @_onFileUploadStoreChange
|
|
|
|
@_usubs.push AccountStore.listen @_onAccountStoreChanged
|
2015-04-29 06:32:15 +08:00
|
|
|
@_keymapUnlisten = atom.commands.add '.composer-outer-wrap', {
|
2015-02-17 09:09:28 +08:00
|
|
|
'composer:send-message': => @_sendDraft()
|
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
|
|
|
'composer:delete-empty-draft': => @_deleteDraftIfEmpty()
|
2015-09-14 22:37:00 +08:00
|
|
|
'composer:show-and-focus-bcc': => @_onChangeEnabledFields(show: [Fields.Bcc], focus: Fields.Bcc)
|
|
|
|
'composer:show-and-focus-cc': => @_onChangeEnabledFields(show: [Fields.Cc], focus: Fields.Cc)
|
|
|
|
'composer:focus-to': => @_onChangeEnabledFields(show: [Fields.To], focus: Fields.To)
|
2015-08-29 06:59:00 +08:00
|
|
|
"composer:undo": @undo
|
|
|
|
"composer:redo": @redo
|
2015-02-11 03:10:14 +08:00
|
|
|
}
|
2015-09-14 22:37:00 +08:00
|
|
|
@_applyFocusedField()
|
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
|
2015-04-29 06:32:15 +08:00
|
|
|
@_keymapUnlisten.dispose() if @_keymapUnlisten
|
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
|
|
|
componentDidUpdate: =>
|
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-09-14 22:37:00 +08:00
|
|
|
@_applyFocusedField()
|
|
|
|
|
|
|
|
_applyFocusedField: ->
|
|
|
|
if @state.focusedField
|
|
|
|
return unless @refs[@state.focusedField]
|
|
|
|
if @refs[@state.focusedField].focus
|
|
|
|
@refs[@state.focusedField].focus()
|
|
|
|
else
|
|
|
|
React.findDOMNode(@refs[@state.focusedField]).focus()
|
2015-06-18 03:29:32 +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
|
|
|
|
|
|
|
_setupSession: (proxy) =>
|
|
|
|
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) ->
|
|
|
|
uploadData = FileUploadStore.linkedUpload(file)
|
|
|
|
if not uploadData? and Utils.looksLikeImage(file)
|
|
|
|
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-05-01 04:08:29 +08:00
|
|
|
render: =>
|
2015-02-28 07:34:37 +08:00
|
|
|
if @props.mode is "inline"
|
2015-10-03 08:14:00 +08:00
|
|
|
<FocusTrackingRegion className={@_wrapClasses()}
|
|
|
|
ref="composer"
|
|
|
|
tabIndex="-1">
|
2015-09-14 22:37:00 +08:00
|
|
|
{@_renderComposer()}
|
2015-08-06 08:38:44 +08:00
|
|
|
</FocusTrackingRegion>
|
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
|
|
|
else
|
2015-10-03 08:14:00 +08:00
|
|
|
<div className={@_wrapClasses()} ref="composer">
|
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
|
|
|
{@_renderComposer()}
|
|
|
|
</div>
|
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_wrapClasses: =>
|
2015-07-31 09:29:38 +08:00
|
|
|
"message-item-white-wrap composer-outer-wrap #{@props.className ? ""}"
|
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
|
|
|
_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>
|
|
|
|
|
|
|
|
_renderContent: ->
|
|
|
|
<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}
|
|
|
|
focusedField={@state.focusedField}
|
|
|
|
enabledFields={@state.enabledFields}
|
|
|
|
onPopoutComposer={@_onPopoutComposer}
|
|
|
|
onChangeParticipants={@_onChangeParticipants}
|
|
|
|
onChangeEnabledFields={@_onChangeEnabledFields} />
|
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}
|
|
|
|
onClick={@_focusParticipantField} />
|
2015-09-14 22:37:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
{@_renderSubject()}
|
|
|
|
|
|
|
|
<div className="compose-body" ref="composeBody" onClick={@_onClickComposeBody}>
|
|
|
|
{@_renderBody()}
|
|
|
|
{@_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()
|
|
|
|
enabledFields = _.filter @state.enabledFields, (field) ->
|
|
|
|
Fields.Order[field] >= 0
|
|
|
|
enabledFields = _.sortBy enabledFields, (field) ->
|
|
|
|
Fields.Order[field]
|
|
|
|
i = enabledFields.indexOf @state.focusedField
|
|
|
|
dir = if event.shiftKey then -1 else 1
|
|
|
|
newI = Math.min(Math.max(i + dir, 0), enabledFields.length - 1)
|
|
|
|
@setState focusedField: enabledFields[newI]
|
|
|
|
event.stopPropagation()
|
2015-04-25 02:33:10 +08:00
|
|
|
|
2015-09-14 22:37:00 +08:00
|
|
|
_onChangeParticipantField: (focusedField) =>
|
|
|
|
@setState {focusedField}
|
|
|
|
@_lastFocusedParticipantField = focusedField
|
|
|
|
|
|
|
|
_focusParticipantField: =>
|
|
|
|
@setState focusedField: @_lastFocusedParticipantField ? Fields.To
|
|
|
|
|
|
|
|
_onChangeEnabledFields: ({show, hide, focus}={}) =>
|
|
|
|
show = show ? []; hide = hide ? []
|
|
|
|
newFields = _.difference(@state.enabledFields.concat(show), hide)
|
|
|
|
@setState
|
|
|
|
enabledFields: newFields
|
|
|
|
focusedField: (focus ? @state.focusedField)
|
|
|
|
|
|
|
|
_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}
|
|
|
|
onFocus={ => @setState focusedField: Fields.Subject}
|
|
|
|
onChange={@_onChangeSubject}/>
|
|
|
|
</div>
|
2015-06-24 06:21:25 +08:00
|
|
|
|
|
|
|
_renderBody: =>
|
2015-10-31 08:03:33 +08:00
|
|
|
<span ref="composerBodyWrap">
|
2015-09-14 22:37:00 +08:00
|
|
|
{@_renderBodyContenteditable()}
|
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
|
|
|
|
2015-09-23 07:02:44 +08:00
|
|
|
_renderBodyContenteditable: ->
|
2015-10-30 05:20:41 +08:00
|
|
|
<Contenteditable
|
2015-09-23 07:02:44 +08:00
|
|
|
ref={Fields.Body}
|
2015-10-31 08:03:33 +08:00
|
|
|
value={@_removeQuotedText(@state.body)}
|
2015-09-23 07:02:44 +08:00
|
|
|
onFocus={ => @setState focusedField: Fields.Body}
|
|
|
|
onChange={@_onChangeBody}
|
|
|
|
onScrollTo={@props.onRequestScrollTo}
|
|
|
|
onFilePaste={@_onFilePaste}
|
|
|
|
onScrollToBottom={@_onScrollToBottom()}
|
2015-10-30 05:20:41 +08:00
|
|
|
lifecycleCallbacks={@_contenteditableLifecycleCallbacks()}
|
2015-10-03 08:14:00 +08:00
|
|
|
getComposerBoundingRect={@_getComposerBoundingRect}
|
2015-09-23 07:02:44 +08:00
|
|
|
initialSelectionSnapshot={@_recoveredSelection} />
|
|
|
|
|
2015-10-30 05:20:41 +08:00
|
|
|
_contenteditableLifecycleCallbacks: ->
|
|
|
|
componentDidUpdate: (editableNode) =>
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
extension.onComponentDidUpdate?(editableNode)
|
|
|
|
|
|
|
|
onInput: (editableNode, event) =>
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
extension.onInput?(editableNode, event)
|
|
|
|
|
|
|
|
onTabDown: (editableNode, event, range) =>
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
extension.onTabDown?(editableNode, range, event)
|
|
|
|
|
|
|
|
onSubstitutionPerformed: (editableNode) =>
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
extension.onSubstitutionPerformed?(editableNode)
|
|
|
|
|
|
|
|
onLearnSpelling: (editableNode, text) =>
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
extension.onLearnSpelling?(editableNode, text)
|
|
|
|
|
|
|
|
onMouseUp: (editableNode, event, range) =>
|
|
|
|
return unless range
|
|
|
|
try
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
extension.onMouseUp?(editableNode, range, event)
|
|
|
|
catch e
|
|
|
|
console.error('DraftStore extension raised an error: '+e.toString())
|
|
|
|
|
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: =>
|
|
|
|
React.findDOMNode(@refs.composer).getBoundingClientRect()
|
|
|
|
|
2015-09-23 07:02:44 +08:00
|
|
|
_onScrollToBottom: ->
|
2015-07-31 09:29:38 +08:00
|
|
|
if @props.onRequestScrollTo
|
2015-09-23 07:02:44 +08:00
|
|
|
return =>
|
2015-10-03 08:14:00 +08:00
|
|
|
@props.onRequestScrollTo
|
|
|
|
clientId: @_proxy.draft().clientId
|
|
|
|
position: ScrollRegion.ScrollPosition.Bottom
|
2015-09-23 07:02:44 +08:00
|
|
|
else return null
|
|
|
|
|
|
|
|
_removeQuotedText: (html) =>
|
|
|
|
if @state.showQuotedText then return html
|
|
|
|
else return QuotedHTMLParser.removeQuotedHTML(html)
|
|
|
|
|
|
|
|
_showQuotedText: (html) =>
|
|
|
|
if @state.showQuotedText then return html
|
|
|
|
else return QuotedHTMLParser.appendQuotedHTML(html, @state.body)
|
|
|
|
|
|
|
|
_renderQuotedTextControl: ->
|
|
|
|
if QuotedHTMLParser.hasQuotedHTML(@state.body)
|
|
|
|
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: ->
|
|
|
|
renderSubset = (arr, attachmentRole, UploadComponent) =>
|
|
|
|
arr.map (fileOrUpload) =>
|
2015-07-16 04:15:05 +08:00
|
|
|
if fileOrUpload instanceof File
|
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
|
|
|
@_attachmentComponent(fileOrUpload, attachmentRole)
|
|
|
|
else
|
2015-07-16 07:52:43 +08:00
|
|
|
<UploadComponent key={fileOrUpload.uploadTaskId} uploadData={fileOrUpload} />
|
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 className="attachments-area">
|
|
|
|
{renderSubset(@_nonImages(), 'Attachment', FileUpload)}
|
|
|
|
{renderSubset(@_images(), 'Attachment:Image', ImageFileUpload)}
|
|
|
|
</div>
|
2015-06-12 02:52:49 +08:00
|
|
|
|
|
|
|
_attachmentComponent: (file, role="Attachment") =>
|
|
|
|
targetPath = FileUploadStore.linkedUpload(file)?.filePath
|
|
|
|
if not targetPath
|
|
|
|
targetPath = FileDownloadStore.pathForFile(file)
|
|
|
|
|
|
|
|
props =
|
|
|
|
file: file
|
|
|
|
removable: true
|
|
|
|
targetPath: targetPath
|
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} />
|
|
|
|
|
|
|
|
_fileSort: (fileOrUpload) ->
|
|
|
|
if fileOrUpload.object is "file"
|
|
|
|
# There will only be an entry in the `linkedUpload` if the file had
|
|
|
|
# finished uploading in this session. We may well have files that
|
|
|
|
# already existed on a draft that don't have any uploadData
|
|
|
|
# associated with them.
|
|
|
|
uploadData = FileUploadStore.linkedUpload(fileOrUpload)
|
|
|
|
else
|
|
|
|
uploadData = fileOrUpload
|
|
|
|
|
|
|
|
if not uploadData
|
|
|
|
sortOrder = 0
|
|
|
|
else
|
2015-07-16 07:52:43 +08:00
|
|
|
sortOrder = (uploadData.startDate / 1) + 1.0 / (uploadData.startId/1)
|
2015-06-12 02:52:49 +08:00
|
|
|
|
|
|
|
return sortOrder
|
|
|
|
|
|
|
|
_images: ->
|
|
|
|
_.sortBy _.filter(@_uploadsAndFiles(), Utils.looksLikeImage), @_fileSort
|
|
|
|
|
|
|
|
_nonImages: ->
|
|
|
|
_.sortBy _.reject(@_uploadsAndFiles(), Utils.looksLikeImage), @_fileSort
|
|
|
|
|
|
|
|
_uploadsAndFiles: ->
|
2015-06-26 22:42:41 +08:00
|
|
|
# When uploads finish, they stay attached to the object at 100%
|
|
|
|
# completion. Eventually the DB trigger will make its way to a window
|
|
|
|
# and the files will appear on the draft.
|
|
|
|
#
|
|
|
|
# In this case we want to show the file instead of the upload
|
|
|
|
uploads = _.filter @state.uploads, (upload) =>
|
|
|
|
for file in @state.files
|
|
|
|
linkedUpload = FileUploadStore.linkedUpload(file)
|
2015-07-16 07:52:43 +08:00
|
|
|
return false if linkedUpload and linkedUpload.uploadTaskId is upload.uploadTaskId
|
2015-06-26 22:42:41 +08:00
|
|
|
return true
|
|
|
|
|
|
|
|
_.compact(uploads.concat(@state.files))
|
2015-06-12 02:52:49 +08:00
|
|
|
|
|
|
|
_onFileUploadStoreChange: =>
|
2015-08-29 02:12:53 +08:00
|
|
|
@setState uploads: FileUploadStore.uploadsForMessage(@props.draftClientId)
|
2015-04-22 09:16:08 +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"
|
2015-06-18 07:03:50 +08:00
|
|
|
onClick={@_attachFile}><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} />
|
|
|
|
|
2015-06-18 07:03:50 +08:00
|
|
|
<button className="btn btn-toolbar btn-emphasis btn-text btn-send" style={order: -100}
|
2015-04-25 02:33:10 +08:00
|
|
|
ref="sendButton"
|
2015-06-18 07:03:50 +08:00
|
|
|
onClick={@_sendDraft}><RetinaImg name="icon-composer-send.png" mode={RetinaImg.Mode.ContentIsMask} /><span className="text">Send</span></button>
|
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-06-18 07:03:50 +08:00
|
|
|
_onClickComposeBody: (event) =>
|
2015-09-14 22:37:00 +08:00
|
|
|
@refs[Fields.Body].selectEnd()
|
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
|
|
|
|
subject: draft.subject
|
|
|
|
|
|
|
|
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
|
|
|
|
return Fields.Body
|
|
|
|
|
|
|
|
_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
|
|
|
|
|
|
|
|
# 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: =>
|
|
|
|
if @_shouldShowFromField(@_proxy?.draft())
|
|
|
|
enabledFields = @state.enabledFields.concat [Fields.From]
|
|
|
|
else
|
|
|
|
enabledFields = _.without(@state.enabledFields, Fields.From)
|
|
|
|
@setState {enabledFields}
|
|
|
|
|
|
|
|
_shouldShowFromField: (draft) ->
|
|
|
|
return false unless draft
|
|
|
|
return AccountStore.items().length > 1 and
|
|
|
|
not draft.replyToMessageId and
|
|
|
|
draft.files.length is 0
|
|
|
|
|
|
|
|
_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
|
|
|
|
existingFilePaths = @state.files.map (f) ->
|
|
|
|
FileUploadStore.linkedUpload(f)?.filePath
|
|
|
|
|
|
|
|
nonNativeFilePath = @_nonNativeFilePathForDrop(event)
|
|
|
|
if nonNativeFilePath and nonNativeFilePath in existingFilePaths
|
|
|
|
return false
|
|
|
|
|
|
|
|
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
|
2015-08-29 02:12:53 +08:00
|
|
|
Actions.attachFilePath({path: 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))
|
2015-08-29 02:12:53 +08:00
|
|
|
Actions.attachFilePath({path: uri, messageClientId: @props.draftClientId})
|
2015-07-16 04:15:05 +08:00
|
|
|
|
2015-06-09 03:41:31 +08:00
|
|
|
_onFilePaste: (path) =>
|
2015-08-29 02:12:53 +08:00
|
|
|
Actions.attachFilePath({path: 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
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_onChangeBody: (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
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_sendDraft: (options = {}) =>
|
2015-05-16 01:45:18 +08:00
|
|
|
return 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.
|
2015-08-29 02:12:53 +08:00
|
|
|
return 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
|
|
|
})
|
|
|
|
return
|
|
|
|
|
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')
|
|
|
|
|
2015-03-21 01:23:50 +08:00
|
|
|
# Check third party warnings added via DraftStore extensions
|
|
|
|
for extension in DraftStore.extensions()
|
|
|
|
continue unless extension.warningsForSending
|
|
|
|
warnings = warnings.concat(extension.warningsForSending(draft))
|
|
|
|
|
2015-02-07 06:46:30 +08:00
|
|
|
if warnings.length > 0 and not options.force
|
|
|
|
dialog.showMessageBox remote.getCurrentWindow(), {
|
|
|
|
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 ')}?"
|
|
|
|
}, (response) =>
|
2015-08-11 03:19:40 +08:00
|
|
|
if response is 0 # response is button array index
|
2015-02-07 06:46:30 +08:00
|
|
|
@_sendDraft({force: true})
|
|
|
|
return
|
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-08-29 02:12:53 +08:00
|
|
|
Actions.sendDraft(@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-06-18 03:29:21 +08:00
|
|
|
_mentionsAttachment: (body) =>
|
2015-07-09 00:51:33 +08:00
|
|
|
body = QuotedHTMLParser.removeQuotedHTML(body.toLowerCase().trim())
|
|
|
|
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
|
|
|
|
2015-05-01 04:08:29 +08:00
|
|
|
_attachFile: =>
|
2015-08-29 02:12:53 +08:00
|
|
|
Actions.attachFile({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
|