Mailspring/internal_packages/composer/lib/composer-view.cjsx

791 lines
26 KiB
Plaintext
Raw Normal View History

_ = require 'underscore'
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
{File,
Utils,
Actions,
DOMUtils,
DraftStore,
UndoManager,
ContactStore,
AccountStore,
FileUploadStore,
QuotedHTMLTransformer,
FileDownloadStore,
FocusedContentStore,
ExtensionRegistry} = require 'nylas-exports'
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
{DropZone,
RetinaImg,
ScrollRegion,
InjectedComponent,
KeyCommandsRegion,
InjectedComponentSet} = require 'nylas-component-kit'
FileUpload = require './file-upload'
ImageFileUpload = require './image-file-upload'
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
ComposerEditor = require './composer-editor'
SendActionButton = require './send-action-button'
ExpandedParticipants = require './expanded-participants'
CollapsedParticipants = require './collapsed-participants'
Fields = require './fields'
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
# Composer with new props.
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
@propTypes:
draftClientId: React.PropTypes.string
# Either "inline" or "fullwindow"
mode: React.PropTypes.string
# If this composer is part of an existing thread (like inline
# composers) the threadId will be handed down
threadId: React.PropTypes.string
# Sometimes when changes in the composer happens it's desirable to
# have the parent scroll to a certain location. A parent component can
# pass a callback that gets called when this composer wants to be
# scrolled to.
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
scrollTo: React.PropTypes.func
constructor: (@props) ->
@state =
populated: false
to: []
cc: []
bcc: []
from: []
body: ""
files: []
uploads: []
subject: ""
accounts: []
focusedField: Fields.To # Gets updated in @_initiallyFocusedField
enabledFields: [] # Gets updated in @_initiallyEnabledFields
showQuotedText: false
componentWillMount: =>
@_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
shouldComponentUpdate: (nextProps, nextState) =>
not Utils.isEqualReact(nextProps, @props) or
not Utils.isEqualReact(nextState, @state)
componentDidMount: =>
@_usubs = []
@_usubs.push AccountStore.listen @_onAccountStoreChanged
@_applyFieldFocus()
componentWillUnmount: =>
@_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>&nbsp;<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\">&nbsp;/&nbsp;</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>&nbsp;<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()
usub() for usub in @_usubs
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
componentDidUpdate: (prevProps, prevState) =>
# 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?
# If the body changed, let's wait for the editor body to actually get rendered
# into the dom before applying focus.
# Since the editor is an InjectedComponent, when this function gets called
# the editor hasn't actually finished rendering, so we need to wait for that
# to happen by using the InjectedComponent's `onComponentDidRender` callback.
# See `_renderEditor`
bodyChanged = @state.body isnt prevState.body
return if bodyChanged
@_applyFieldFocus()
focus: =>
@_applyFieldFocus()
_keymapHandlers: ->
'composer:send-message': => @_onPrimarySend()
'composer:delete-empty-draft': => @_deleteDraftIfEmpty()
'composer:show-and-focus-bcc': =>
@_onAdjustEnabledFields(show: [Fields.Bcc])
'composer:show-and-focus-cc': =>
@_onAdjustEnabledFields(show: [Fields.Cc])
'composer:focus-to': =>
@_onAdjustEnabledFields(show: [Fields.To])
"composer:show-and-focus-from": => # TODO
"composer:undo": @undo
"composer:redo": @redo
_applyFieldFocus: =>
@_applyFieldFocusTo(@state.focusedField)
_applyFieldFocusTo: (fieldName) =>
return unless @refs[fieldName]
$el = React.findDOMNode(@refs[fieldName])
return if document.activeElement is $el or $el.contains(document.activeElement)
if @refs[fieldName].focus
@refs[fieldName].focus()
else
$el.select()
$el.focus()
componentWillReceiveProps: (newProps) =>
@_ignoreNextTrigger = false
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()
@_prepareForDraft(newProps.draftClientId)
_prepareForDraft: (draftClientId) =>
@unlisteners = []
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
# UndoManager must be ready before we call _onDraftChanged for the first time
@undoManager = new UndoManager
DraftStore.sessionForClientId(draftClientId).then(@_setupSession)
2015-11-07 04:08:32 +08:00
_setupSession: (proxy) =>
return if @_unmounted
return unless proxy.draftClientId is @props.draftClientId
@_proxy = proxy
@_preloadImages(@_proxy.draft()?.files)
@unlisteners.push @_proxy.listen(@_onDraftChanged)
@_onDraftChanged()
_preloadImages: (files=[]) ->
files.forEach (file) ->
if Utils.shouldDisplayAsImage(file)
Actions.fetchFile(file)
_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
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
render: ->
classes = "message-item-white-wrap composer-outer-wrap #{@props.className ? ""}"
<KeyCommandsRegion
localHandlers={@_keymapHandlers()}
className={classes}
onFocusIn={@_onFocusIn}
tabIndex="-1"
ref="composerWrap">
{@_renderComposer()}
</KeyCommandsRegion>
_renderComposer: =>
<DropZone className="composer-inner-wrap"
shouldAcceptDrop={@_shouldAcceptDrop}
onDragStateChange={ ({isDropping}) => @setState({isDropping}) }
onDrop={@_onDrop}>
<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>
</div>
<div className="composer-content-wrap" onKeyDown={@_onKeyDown}>
{@_renderScrollRegion()}
2015-03-05 07:52:40 +08:00
</div>
<div className="composer-action-bar-wrap">
{@_renderActionsRegion()}
</div>
</DropZone>
2015-03-06 05:31:11 +08:00
_renderScrollRegion: ->
if @props.mode is "inline"
@_renderContent()
else
<ScrollRegion className="compose-body-scroll" ref="scrollregion">
{@_renderContent()}
</ScrollRegion>
feat(account-prefs): Adds new page for Account in preferences Summary: Adds the new Account preferences page. This consists of two major React components, PreferencesAccountList and PreferencesAccountDetails, both of which use EditableList. I added a bunch of fixes and updated the API for EditableList, plus a bit of refactoring for PreferencesAccount component, and a bunch of CSS so its a big diff. The detailed changelog: Updates to EditableList: - Fix bug updating selection state when arrows pressed to move selection - Add new props: - allowEmptySelection to allow the list to have no selection - createInputProps to pass aditional props to the createInput - Add scroll region for list items - Update styles and refactor render methods Other Updates: - Updates Account model to hold aliases and a label - Adds getter for label to default to email - Update accountswitcher to display label, update styles and spec - Refactor PreferencesAccounts component: - Splits it into smaller components, - Removes unused code - Splits preferences styelsheets into smaller separate stylesheet for account page. Adds some updates and fixes (scroll-region padding) - Update AccountStore to be able to perform updates on an account. - Adds new Action to update account, and an action to remove account to be consistent with Action usage - Adds components for Account list and Aliases list using EditableList Test Plan: - All specs pass, but need to write new tests! Reviewers: bengotow, evan Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2332
2015-12-10 04:35:40 +08:00
_renderContent: =>
<div className="composer-centered">
{if @state.focusedField in Fields.ParticipantFields
<ExpandedParticipants
to={@state.to} cc={@state.cc} bcc={@state.bcc}
from={@state.from}
ref="expandedParticipants"
mode={@props.mode}
accounts={@state.accounts}
focusedField={@state.focusedField}
enabledFields={@state.enabledFields}
onPopoutComposer={@_onPopoutComposer}
onChangeParticipants={@_onChangeParticipants}
onChangeFocusedField={@_onChangeFocusedField}
onAdjustEnabledFields={@_onAdjustEnabledFields} />
else
<CollapsedParticipants
to={@state.to} cc={@state.cc} bcc={@state.bcc}
onClick={@_onExpandParticipantFields} />
}
{@_renderSubject()}
<div className="compose-body"
ref="composeBody"
onMouseUp={@_onMouseUpComposerBody}
onMouseDown={@_onMouseDownComposerBody}>
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
{@_renderBodyRegions()}
{@_renderFooterRegions()}
</div>
</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
_onPopoutComposer: =>
Actions.composePopoutDraft @props.draftClientId
_onKeyDown: (event) =>
if event.key is "Tab"
@_onTabDown(event)
return
_onTabDown: (event) =>
event.preventDefault()
event.stopPropagation()
@_onShiftFocusedField(if event.shiftKey then -1 else 1)
_onShiftFocusedField: (dir) =>
enabledFields = _.filter @state.enabledFields, (field) ->
Fields.Order[field] >= 0
enabledFields = _.sortBy enabledFields, (field) ->
Fields.Order[field]
i = enabledFields.indexOf @state.focusedField
newI = Math.min(Math.max(i + dir, 0), enabledFields.length - 1)
@_onChangeFocusedField(enabledFields[newI])
_onChangeFocusedField: (focusedField) =>
@setState({focusedField})
if focusedField in Fields.ParticipantFields
@_lastFocusedParticipantField = focusedField
_onExpandParticipantFields: =>
@_onChangeFocusedField(@_lastFocusedParticipantField ? Fields.To)
_onAdjustEnabledFields: ({show, hide}={}) =>
show = show ? []; hide = hide ? []
enabledFields = _.difference(@state.enabledFields.concat(show), hide)
if hide.length > 0 and enabledFields.indexOf(@state.focusedField) is -1
@_onShiftFocusedField(-1)
@setState({enabledFields})
if show.length > 0
@_onChangeFocusedField(show[0])
_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>
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
_renderBodyRegions: =>
<span ref="composerBodyWrap">
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
{@_renderEditor()}
{@_renderQuotedTextControl()}
{@_renderAttachments()}
</span>
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
_renderEditor: ->
exposedProps =
body: @_removeQuotedText(@state.body)
draftClientId: @props.draftClientId
parentActions: {
getComposerBoundingRect: @_getComposerBoundingRect
scrollTo: @props.scrollTo
}
initialSelectionSnapshot: @_recoveredSelection
onFocus: => @setState(focusedField: Fields.Body)
onBlur: => @setState(focusedField: null)
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
onFilePaste: @_onFilePaste
onBodyChanged: @_onBodyChanged
# TODO Get rid of the unecessary required methods:
# getCurrentSelection and getPreviousSelection shouldn't be needed and
# undo/redo functionality should be refactored into ComposerEditor
# _onDOMMutated is just for testing purposes, refactor the tests
<InjectedComponent
ref={Fields.Body}
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
matching={role: "Composer:Editor"}
fallback={ComposerEditor}
onComponentDidRender={@_onEditorBodyDidRender}
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
requiredMethods={[
'focus'
'nativeFocus'
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
'getCurrentSelection'
'getPreviousSelection'
'_onDOMMutated'
]}
exposedProps={exposedProps} />
_onEditorBodyDidRender: =>
@_applyFieldFocus()
# 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.composerWrap).getBoundingClientRect()
_removeQuotedText: (html) =>
if @state.showQuotedText then return html
else return QuotedHTMLTransformer.removeQuotedHTML(html)
_showQuotedText: (html) =>
if @state.showQuotedText then return html
else return QuotedHTMLTransformer.appendQuotedHTML(html, @state.body)
_renderQuotedTextControl: ->
if QuotedHTMLTransformer.hasQuotedHTML(@state.body)
text = if @state.showQuotedText then "Hide" else "Show"
<a className="quoted-text-control" onClick={@_onToggleQuotedText}>
<span className="dots">&bull;&bull;&bull;</span>{text} previous
</a>
else return []
_onToggleQuotedText: =>
@setState showQuotedText: not @state.showQuotedText
_renderFooterRegions: =>
return <div></div> unless @props.draftClientId
<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"}
exposedProps={draftClientId:@props.draftClientId, threadId: @props.threadId}/>
</div>
feat(offline-mode, undo-redo): Tasks handle network errors better and retry, undo/redo based on tasks Summary: This diff does a couple things: - Undo redo with a new undo/redo store that maintains it's own queue of undo/redo tasks. This queue is separate from the TaskQueue because not all tasks should be considered for undo history! Right now just the AddRemoveTagsTask is undoable. - NylasAPI.makeRequest now returns a promise which resolves with the result or rejects with an error. For things that still need them, there's still `success` and `error` callbacks. I also added `started:(req) ->` which allows you to get the underlying request. - Aborting a NylasAPI request now makes it call it's error callback / promise reject. - You can now run code after perform local has completed using this syntax: ``` task = new AddRemoveTagsTask(focused, ['archive'], ['inbox']) task.waitForPerformLocal().then -> Actions.setFocus(collection: 'thread', item: nextFocus) Actions.setCursorPosition(collection: 'thread', item: nextKeyboard) Actions.queueTask(task) ``` - In specs, you can now use `advanceClock` to get through a Promise.then/catch/finally. Turns out it was using something low level and not using setTimeout(0). - The TaskQueue uses promises better and defers a lot of the complexity around queueState for performLocal/performRemote to a task subclass called APITask. APITask implements "perform" and breaks it into "performLocal" and "performRemote". - All tasks either resolve or reject. They're always removed from the queue, unless they resolve with Task.Status.Retry, which means they internally did a .catch (err) => Promise.resolve(Task.Status.Retry) and they want to be run again later. - API tasks retry until they succeed or receive a NylasAPI.PermanentErrorCode (400,404,500), in which case they revert and finish. - The AddRemoveTags Task can now take more than one thread! This is super cool because you can undo/redo a bulk action and also because we'll probably have a bulk tag modification API endpoint soon. Getting undo / redo working revealed that the thread versioning system we built isn't working because the server was incrementing things by more than 1 at a time. Now we count the number of unresolved "optimistic" changes we've made to a given model, and only accept the server's version of it once the number of optimistic changes is back at zero. Known Issues: - AddRemoveTagsTasks aren't dependent on each other, so if you (undo/redo x lots) and then come back online, all the tasks try to add / remove all the tags at the same time. To fix this we can either allow the tasks to be merged together into a minimal set or make them block on each other. - When Offline, you still get errors in the console for GET requests. Need to catch these and display an offline status bar. - The metadata tasks haven't been updated yet to the new API. Wanted to get it reviewed first! Test Plan: All the tests still pass! Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1694
2015-07-08 01:38:53 +08:00
_renderAttachments: ->
<div className="attachments-area">
{@_renderFileAttachments()}
{@_renderUploadAttachments()}
feat(offline-mode, undo-redo): Tasks handle network errors better and retry, undo/redo based on tasks Summary: This diff does a couple things: - Undo redo with a new undo/redo store that maintains it's own queue of undo/redo tasks. This queue is separate from the TaskQueue because not all tasks should be considered for undo history! Right now just the AddRemoveTagsTask is undoable. - NylasAPI.makeRequest now returns a promise which resolves with the result or rejects with an error. For things that still need them, there's still `success` and `error` callbacks. I also added `started:(req) ->` which allows you to get the underlying request. - Aborting a NylasAPI request now makes it call it's error callback / promise reject. - You can now run code after perform local has completed using this syntax: ``` task = new AddRemoveTagsTask(focused, ['archive'], ['inbox']) task.waitForPerformLocal().then -> Actions.setFocus(collection: 'thread', item: nextFocus) Actions.setCursorPosition(collection: 'thread', item: nextKeyboard) Actions.queueTask(task) ``` - In specs, you can now use `advanceClock` to get through a Promise.then/catch/finally. Turns out it was using something low level and not using setTimeout(0). - The TaskQueue uses promises better and defers a lot of the complexity around queueState for performLocal/performRemote to a task subclass called APITask. APITask implements "perform" and breaks it into "performLocal" and "performRemote". - All tasks either resolve or reject. They're always removed from the queue, unless they resolve with Task.Status.Retry, which means they internally did a .catch (err) => Promise.resolve(Task.Status.Retry) and they want to be run again later. - API tasks retry until they succeed or receive a NylasAPI.PermanentErrorCode (400,404,500), in which case they revert and finish. - The AddRemoveTags Task can now take more than one thread! This is super cool because you can undo/redo a bulk action and also because we'll probably have a bulk tag modification API endpoint soon. Getting undo / redo working revealed that the thread versioning system we built isn't working because the server was incrementing things by more than 1 at a time. Now we count the number of unresolved "optimistic" changes we've made to a given model, and only accept the server's version of it once the number of optimistic changes is back at zero. Known Issues: - AddRemoveTagsTasks aren't dependent on each other, so if you (undo/redo x lots) and then come back online, all the tasks try to add / remove all the tags at the same time. To fix this we can either allow the tasks to be merged together into a minimal set or make them block on each other. - When Offline, you still get errors in the console for GET requests. Need to catch these and display an offline status bar. - The metadata tasks haven't been updated yet to the new API. Wanted to get it reviewed first! Test Plan: All the tests still pass! Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1694
2015-07-08 01:38:53 +08:00
</div>
_renderFileAttachments: ->
2016-01-28 05:49:34 +08:00
nonImageFiles = @_nonImageFiles(@state.files).map((file) =>
@_renderFileAttachment(file, "Attachment")
)
2016-01-28 05:49:34 +08:00
imageFiles = @_imageFiles(@state.files).map((file) =>
@_renderFileAttachment(file, "Attachment:Image")
)
nonImageFiles.concat(imageFiles)
_renderFileAttachment: (file, role) ->
props =
file: file
removable: true
targetPath: FileDownloadStore.pathForFile(file)
messageClientId: @props.draftClientId
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"
feat(attachments): Tons of tiny fixes to attachments, drag-and-drop attachments to other apps Summary: consolidate all the "untitled" stuff into a convenience method on the File itself. Previously it'd show "Unnamed Attachment", download as "Untitled" and open as "<file.id>". Now it's consistent everywhere and chooses names based on the contenttype (Event.ics). Rewrite CSS rules for uploads and attachments to be simpler - remove container divs and classnames from things that have no CSS - switch to using Flexbox so it's not necesary to have so many containers - remove zIndex hacks, apply overflow rules to name div only, so long filenames don't make action button unclickable - consolidate CSS classnames for uploads/attachments - Other style fixes - cursor "default" instead of text insertion on image attachments - cursor "default" on action buttons - image uplaods / attachments with long filenames truncate with ellpsis - attachments are not indented by an extra 15px in message bodies Prevent progress bar overflow (was ending above 100%, 100.12315%...) Update FileDownloadStore so it never creates Download objects when file is downloaded already - Previously, the download itself decided if it would be a no-op, but this meant the download was around for a split second and you'd see progress indicators flash for a moment when opening/saving an attachment. Upgrade FileDownloadStore use of promises Restore Image attachment drag and drop - was broken because the name gradient thing was covering the entire drag region. Allow file attachments to be drag and dropped to the finder and other applications 😍😍😍 Test Plan: Tests still pass Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1745
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
feat(attachments): Tons of tiny fixes to attachments, drag-and-drop attachments to other apps Summary: consolidate all the "untitled" stuff into a convenience method on the File itself. Previously it'd show "Unnamed Attachment", download as "Untitled" and open as "<file.id>". Now it's consistent everywhere and chooses names based on the contenttype (Event.ics). Rewrite CSS rules for uploads and attachments to be simpler - remove container divs and classnames from things that have no CSS - switch to using Flexbox so it's not necesary to have so many containers - remove zIndex hacks, apply overflow rules to name div only, so long filenames don't make action button unclickable - consolidate CSS classnames for uploads/attachments - Other style fixes - cursor "default" instead of text insertion on image attachments - cursor "default" on action buttons - image uplaods / attachments with long filenames truncate with ellpsis - attachments are not indented by an extra 15px in message bodies Prevent progress bar overflow (was ending above 100%, 100.12315%...) Update FileDownloadStore so it never creates Download objects when file is downloaded already - Previously, the download itself decided if it would be a no-op, but this meant the download was around for a split second and you'd see progress indicators flash for a moment when opening/saving an attachment. Upgrade FileDownloadStore use of promises Restore Image attachment drag and drop - was broken because the name gradient thing was covering the entire drag region. Allow file attachments to be drag and dropped to the finder and other applications 😍😍😍 Test Plan: Tests still pass Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1745
2015-07-16 04:15:05 +08:00
className = "file-wrap file-image-wrap"
<InjectedComponent key={file.id}
matching={role: role}
className={className}
exposedProps={props} />
_renderUploadAttachments: ->
nonImageUploads = @_nonImageFiles(@state.uploads).map((upload) ->
<FileUpload key={upload.id} upload={upload} />
)
imageUploads = @_imageFiles(@state.uploads).map((upload) ->
2016-01-27 08:57:03 +08:00
<ImageFileUpload key={upload.id} upload={upload} />
)
nonImageUploads.concat(imageUploads)
_imageFiles: (files) ->
_.filter(files, Utils.shouldDisplayAsImage)
_nonImageFiles: (files) ->
_.reject(files, Utils.shouldDisplayAsImage)
_renderActionsRegion: =>
return <div></div> unless @props.draftClientId
<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"}
exposedProps={draftClientId:@props.draftClientId, threadId: @props.threadId}>
<button className="btn btn-toolbar btn-trash" style={order: 100}
title="Delete draft"
onClick={@_destroyDraft}><RetinaImg name="icon-composer-trash.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
<button className="btn btn-toolbar btn-attach" style={order: 50}
title="Attach file"
onClick={@_selectAttachment}><RetinaImg name="icon-composer-attachment.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
<div style={order: 0, flex: 1} />
<SendActionButton draft={@_proxy?.draft()}
ref="sendActionButton"
isValidDraft={@_isValidDraft} />
</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
isForwardedMessage: =>
return false if not @_proxy
draft = @_proxy.draft()
Utils.isForwardedMessage(draft)
# This lets us click outside of the `contenteditable`'s `contentBody`
# and simulate what happens when you click beneath the text *in* the
# contentEditable.
#
# Unfortunately, we need to manually keep track of the "click" in
# separate mouseDown, mouseUp events because we need to ensure that the
# start and end target are both not in the contenteditable. This ensures
# that this behavior doesn't interfear with a click and drag selection.
_onMouseDownComposerBody: (event) =>
if React.findDOMNode(@refs[Fields.Body]).contains(event.target)
@_mouseDownTarget = null
else @_mouseDownTarget = event.target
_onMouseUpComposerBody: (event) =>
if event.target is @_mouseDownTarget
# We don't set state directly here because we want the native
# contenteditable focus behavior. When the contenteditable gets focused
# the focused field state will be properly set via editor.onFocus
@refs[Fields.Body].nativeFocus()
@_mouseDownTarget = null
# When a user focuses the composer, it's possible that no input is
# initially focused. If this happens, we focus the contenteditable. If
# we didn't focus the contenteditable, the user may start typing and
# erroneously trigger keyboard shortcuts.
_onFocusIn: (event) =>
return if DOMUtils.closest(event.target, DOMUtils.inputTypes())
@setState(focusedField: @_initiallyFocusedField(@_proxy.draft()))
_onMouseMoveComposeBody: (event) =>
if @_mouseComposeBody is "down" then @_mouseComposeBody = "move"
_onDraftChanged: =>
return if @_ignoreNextTrigger
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()
if not @_initialHistorySave
@_saveToHistory()
@_initialHistorySave = true
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
state =
to: draft.to
cc: draft.cc
bcc: draft.bcc
from: draft.from
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
uploads: draft.uploads
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
subject: draft.subject
accounts: @_getAccountsForSend()
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
if !@state.populated
_.extend state,
populated: true
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
state = @_verifyEnabledFields(draft, state)
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)
_initiallyFocusedField: (draft) ->
return Fields.To if draft.to.length is 0
return Fields.Subject if (draft.subject ? "").trim().length is 0
shouldFocusBody = @props.mode isnt 'inline' or draft.pristine or
(FocusedContentStore.didFocusUsingClick('thread') is true)
return Fields.Body if shouldFocusBody
return null
_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
_getAccountsForSend: =>
if @_proxy.draft()?.replyToMessageId
[AccountStore.accountForId(@_proxy.draft().accountId)]
else
AccountStore.accounts()
# 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: =>
accounts = @_getAccountsForSend()
feat(account-prefs): Adds new page for Account in preferences Summary: Adds the new Account preferences page. This consists of two major React components, PreferencesAccountList and PreferencesAccountDetails, both of which use EditableList. I added a bunch of fixes and updated the API for EditableList, plus a bit of refactoring for PreferencesAccount component, and a bunch of CSS so its a big diff. The detailed changelog: Updates to EditableList: - Fix bug updating selection state when arrows pressed to move selection - Add new props: - allowEmptySelection to allow the list to have no selection - createInputProps to pass aditional props to the createInput - Add scroll region for list items - Update styles and refactor render methods Other Updates: - Updates Account model to hold aliases and a label - Adds getter for label to default to email - Update accountswitcher to display label, update styles and spec - Refactor PreferencesAccounts component: - Splits it into smaller components, - Removes unused code - Splits preferences styelsheets into smaller separate stylesheet for account page. Adds some updates and fixes (scroll-region padding) - Update AccountStore to be able to perform updates on an account. - Adds new Action to update account, and an action to remove account to be consistent with Action usage - Adds components for Account list and Aliases list using EditableList Test Plan: - All specs pass, but need to write new tests! Reviewers: bengotow, evan Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2332
2015-12-10 04:35:40 +08:00
enabledFields = if @_shouldShowFromField(@_proxy?.draft())
@state.enabledFields.concat [Fields.From]
else
feat(account-prefs): Adds new page for Account in preferences Summary: Adds the new Account preferences page. This consists of two major React components, PreferencesAccountList and PreferencesAccountDetails, both of which use EditableList. I added a bunch of fixes and updated the API for EditableList, plus a bit of refactoring for PreferencesAccount component, and a bunch of CSS so its a big diff. The detailed changelog: Updates to EditableList: - Fix bug updating selection state when arrows pressed to move selection - Add new props: - allowEmptySelection to allow the list to have no selection - createInputProps to pass aditional props to the createInput - Add scroll region for list items - Update styles and refactor render methods Other Updates: - Updates Account model to hold aliases and a label - Adds getter for label to default to email - Update accountswitcher to display label, update styles and spec - Refactor PreferencesAccounts component: - Splits it into smaller components, - Removes unused code - Splits preferences styelsheets into smaller separate stylesheet for account page. Adds some updates and fixes (scroll-region padding) - Update AccountStore to be able to perform updates on an account. - Adds new Action to update account, and an action to remove account to be consistent with Action usage - Adds components for Account list and Aliases list using EditableList Test Plan: - All specs pass, but need to write new tests! Reviewers: bengotow, evan Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2332
2015-12-10 04:35:40 +08:00
_.without(@state.enabledFields, Fields.From)
@setState {enabledFields, accounts}
_shouldShowFromField: (draft) =>
return true if draft
return false
_shouldEnableSubject: =>
return false unless @_proxy
draft = @_proxy.draft()
if _.isEmpty(draft.subject ? "") then return true
else if @isForwardedMessage() then return true
else if draft.replyToMessageId then return false
else return true
_shouldAcceptDrop: (event) =>
# Ensure that you can't pick up a file and drop it on the same draft
nonNativeFilePath = @_nonNativeFilePathForDrop(event)
hasNativeFile = event.dataTransfer.files.length > 0
hasNonNativeFilePath = nonNativeFilePath isnt null
return hasNativeFile or hasNonNativeFilePath
_nonNativeFilePathForDrop: (event) =>
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
_onDrop: (e) =>
feat(attachments): Tons of tiny fixes to attachments, drag-and-drop attachments to other apps Summary: consolidate all the "untitled" stuff into a convenience method on the File itself. Previously it'd show "Unnamed Attachment", download as "Untitled" and open as "<file.id>". Now it's consistent everywhere and chooses names based on the contenttype (Event.ics). Rewrite CSS rules for uploads and attachments to be simpler - remove container divs and classnames from things that have no CSS - switch to using Flexbox so it's not necesary to have so many containers - remove zIndex hacks, apply overflow rules to name div only, so long filenames don't make action button unclickable - consolidate CSS classnames for uploads/attachments - Other style fixes - cursor "default" instead of text insertion on image attachments - cursor "default" on action buttons - image uplaods / attachments with long filenames truncate with ellpsis - attachments are not indented by an extra 15px in message bodies Prevent progress bar overflow (was ending above 100%, 100.12315%...) Update FileDownloadStore so it never creates Download objects when file is downloaded already - Previously, the download itself decided if it would be a no-op, but this meant the download was around for a split second and you'd see progress indicators flash for a moment when opening/saving an attachment. Upgrade FileDownloadStore use of promises Restore Image attachment drag and drop - was broken because the name gradient thing was covering the entire drag region. Allow file attachments to be drag and dropped to the finder and other applications 😍😍😍 Test Plan: Tests still pass Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1745
2015-07-16 04:15:05 +08:00
# Accept drops of real files from other applications
for file in e.dataTransfer.files
Actions.addAttachment({filePath: file.path, messageClientId: @props.draftClientId})
feat(attachments): Tons of tiny fixes to attachments, drag-and-drop attachments to other apps Summary: consolidate all the "untitled" stuff into a convenience method on the File itself. Previously it'd show "Unnamed Attachment", download as "Untitled" and open as "<file.id>". Now it's consistent everywhere and chooses names based on the contenttype (Event.ics). Rewrite CSS rules for uploads and attachments to be simpler - remove container divs and classnames from things that have no CSS - switch to using Flexbox so it's not necesary to have so many containers - remove zIndex hacks, apply overflow rules to name div only, so long filenames don't make action button unclickable - consolidate CSS classnames for uploads/attachments - Other style fixes - cursor "default" instead of text insertion on image attachments - cursor "default" on action buttons - image uplaods / attachments with long filenames truncate with ellpsis - attachments are not indented by an extra 15px in message bodies Prevent progress bar overflow (was ending above 100%, 100.12315%...) Update FileDownloadStore so it never creates Download objects when file is downloaded already - Previously, the download itself decided if it would be a no-op, but this meant the download was around for a split second and you'd see progress indicators flash for a moment when opening/saving an attachment. Upgrade FileDownloadStore use of promises Restore Image attachment drag and drop - was broken because the name gradient thing was covering the entire drag region. Allow file attachments to be drag and dropped to the finder and other applications 😍😍😍 Test Plan: Tests still pass Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1745
2015-07-16 04:15:05 +08:00
# Accept drops from attachment components / images within the app
if (uri = @_nonNativeFilePathForDrop(e))
Actions.addAttachment({filePath: uri, messageClientId: @props.draftClientId})
feat(attachments): Tons of tiny fixes to attachments, drag-and-drop attachments to other apps Summary: consolidate all the "untitled" stuff into a convenience method on the File itself. Previously it'd show "Unnamed Attachment", download as "Untitled" and open as "<file.id>". Now it's consistent everywhere and chooses names based on the contenttype (Event.ics). Rewrite CSS rules for uploads and attachments to be simpler - remove container divs and classnames from things that have no CSS - switch to using Flexbox so it's not necesary to have so many containers - remove zIndex hacks, apply overflow rules to name div only, so long filenames don't make action button unclickable - consolidate CSS classnames for uploads/attachments - Other style fixes - cursor "default" instead of text insertion on image attachments - cursor "default" on action buttons - image uplaods / attachments with long filenames truncate with ellpsis - attachments are not indented by an extra 15px in message bodies Prevent progress bar overflow (was ending above 100%, 100.12315%...) Update FileDownloadStore so it never creates Download objects when file is downloaded already - Previously, the download itself decided if it would be a no-op, but this meant the download was around for a split second and you'd see progress indicators flash for a moment when opening/saving an attachment. Upgrade FileDownloadStore use of promises Restore Image attachment drag and drop - was broken because the name gradient thing was covering the entire drag region. Allow file attachments to be drag and dropped to the finder and other applications 😍😍😍 Test Plan: Tests still pass Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1745
2015-07-16 04:15:05 +08:00
_onFilePaste: (path) =>
Actions.addAttachment({filePath: path, messageClientId: @props.draftClientId})
_onChangeParticipants: (changes={}) =>
@_addToProxy(changes)
_onChangeSubject: (event) =>
@_addToProxy(subject: event.target.value)
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
_onBodyChanged: (event) =>
return unless @_proxy
newBody = @_showQuotedText(event.target.value)
# 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.
@_addToProxy({body: newBody}, {fromBodyChange: true})
@_throttledTrigger ?= _.debounce =>
@_ignoreNextTrigger = false
@_proxy.trigger()
, 100
@_throttledTrigger()
return
_addToProxy: (changes={}, source={}) =>
return unless @_proxy
selections = @_getSelections()
oldDraft = @_proxy.draft()
return if _.all changes, (change, key) -> _.isEqual(change, oldDraft[key])
# 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
@_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
@_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
_isValidDraft: (options = {}) =>
return false unless @_proxy
# 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.
return false if DraftStore.isSendingDraft(@props.draftClientId)
draft = @_proxy.draft()
{remote} = require('electron')
dialog = remote.require('dialog')
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
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
buttons: ['Edit Message'],
message: 'Cannot Send',
detail: dealbreaker
})
return false
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>&nbsp;<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\">&nbsp;/&nbsp;</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>&nbsp;<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()
forwarded = Utils.isForwardedMessage(draft)
hasAttachment = (draft.files ? []).length > 0 or (draft.uploads ? []).length > 0
warnings = []
if draft.subject.length is 0
warnings.push('without a subject line')
if @_mentionsAttachment(draft.body) and not hasAttachment
warnings.push('without an attachment')
if bodyIsEmpty and not forwarded and not hasAttachment
warnings.push('without a body')
feat(editor-region): Add support to register components as editors Summary: - The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come) - Refactors ComposerView and Contenteditable -> - Replaces Contenteditable with an InjectedComponent for a new region role: "Composer:Editor" - Creates a new component called ComposerEditor, which is the one that is being registered by default as "Composer:Editor" - I used this class to try to standardize the props that should be passed to any would be editor Component: - Renamed a bunch of the props which (I think) had a bit of confusing names - Added a bunch of docs for these in the source file, although I feel like those docs should live elsewhere, like in the ComponentRegion docs. - In the process, I ended up pulling some stuff out of ComposerView and some stuff out of the Contenteditable, namely: - The scrolling logic to ensure that the composer is visible while typing was moved outside of the Contenteditable -- this feels more like the ComposerEditor's responsibility, especially since the Contenteditable is meant to be used in other contexts as well. - The ComposerExtensions state; it feels less awkward for me if this is inside the ComposerEditor because 1) ComposerView does less things, 2) these are actually just being passed to the Contenteditable, 3) I feel like other plugins shouldn't need to mess around with ComposerExtensions, so we shouldn't pass them to the editor. If you register an editor different from our default one, any other ComposerExtension callbacks will be disabled, which I feel is expected behavior. - I think there is still some more refactoring to be done, and I left some TODOS here and there, but I think this diff is already big enough and its a minimal set of changes to get the markdown editor working in a not so duck tapish way. - New props for InjectedComponent: - `requiredMethods`: allows you to define a collection of methods that should be implemented by any Component that registers for your desired region. - It will throw an error if these are not implemented - It will automatically pass calls made on the InjectedComponent to these methods down to the instance of the actual registered component - Would love some comments on this approach and impl - `fallback`: allows you to define a default component to use if none were registered through the ComponentRegistry - Misc: - Added a new test case for the QuotedHTMLTransformer - Tests: - They were minimally updated so that they don't break, but a big TODO is to properly refactor them. I plan to do that in an upcoming diff. Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2372
2015-12-19 03:03:58 +08:00
# Check third party warnings added via Composer extensions
for extension in ExtensionRegistry.Composer.extensions()
continue unless extension.warningsForSending
warnings = warnings.concat(extension.warningsForSending({draft}))
if warnings.length > 0 and not options.force
response = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
buttons: ['Send Anyway', 'Cancel'],
message: 'Are you sure?',
detail: "Send #{warnings.join(' and ')}?"
})
if response is 0 # response is button array index
return @_isValidDraft({force: true})
else return false
return true
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
_onPrimarySend: ->
@refs["sendActionButton"].primaryClick()
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
_mentionsAttachment: (body) =>
body = QuotedHTMLTransformer.removeQuotedHTML(body.toLowerCase().trim())
signatureIndex = body.indexOf('<div class="nylas-n1-signature">')
body = body[...signatureIndex] if signatureIndex isnt -1
return body.indexOf("attach") >= 0
_destroyDraft: =>
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
_selectAttachment: =>
Actions.selectAttachment({messageClientId: @props.draftClientId})
undo: (event) =>
event.preventDefault()
event.stopPropagation()
historyItem = @undoManager.undo() ? {}
return unless historyItem.state?
@_recoveredSelection = historyItem.currentSelection
@_addToProxy historyItem.state, fromUndoManager: true
@_recoveredSelection = null
redo: (event) =>
event.preventDefault()
event.stopPropagation()
historyItem = @undoManager.redo() ? {}
return unless historyItem.state?
@_recoveredSelection = historyItem.currentSelection
@_addToProxy historyItem.state, fromUndoManager: true
@_recoveredSelection = null
_getSelections: =>
currentSelection: @refs[Fields.Body]?.getCurrentSelection?()
previousSelection: @refs[Fields.Body]?.getPreviousSelection?()
_saveToHistory: (selections) =>
return unless @_proxy
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)
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>&nbsp;<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\">&nbsp;/&nbsp;</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>&nbsp;<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: =>
return unless @_proxy
if @_proxy.draft().pristine then Actions.destroyDraft(@props.draftClientId)
module.exports = ComposerView