Mailspring/internal_packages/message-list/lib/message-item-container.cjsx
Ben Gotow b4434f6617 fix(focus): Remove focusedField in favor of imperative focus, break apart ComposerView
Summary:
- Removes controlled focus in the composer!
  - No React components ever perfom focus in lifecycle methods. Never again.
  - A new `Utils.schedule({action, after, timeout})` helper makes it easy to say "setState or load draft, etc. and then focus"
  - The DraftStore issues a focusDraft action after creating a draft, which causes the MessageList to focus and scroll to the desired composer, which itself decides which field to focus.
  - The MessageList never focuses anything automatically.
- Refactors ComposerView apart — ComposerHeader handles all top fields, DraftSessionContainer handles draft session initialization and exposes props to ComposerView
  - ComposerHeader now uses a KeyCommandRegion (with focusIn and focusOut) to do the expanding and collapsing of the participants fields. May rename that container very soon.
- Removes all CommandRegistry handling of tab and shift-tab. Unless you preventDefault, the browser does it's thing.
- Removes all tabIndexes greater than 1. This is an anti-pattern—assigning everything a tabIndex of 0 tells the browser to move between them based on their order in the DOM, and is almost always what you want.
- Adds "TabGroupRegion" which allows you to create a tab/shift-tabbing group, (so tabbing does not leave the active composer). Can't believe this isn't a browser feature.

Todos:
- Occasionally, clicking out of the composer contenteditable requires two clicks. This is because atomicEdit is restoring selection within the contenteditable and breaking blur.
- Because the ComposerView does not render until it has a draft, we're back to it being white in popout composers for a brief moment. We will fix this another way - all the "return unless draft" statements were untenable.
- Clicking a row in the thread list no longer shifts focus to the message list and focuses the last draft. This will be restored soon.

Test Plan: Broken

Reviewers: juan, evan

Reviewed By: juan, evan

Differential Revision: https://phab.nylas.com/D2814
2016-04-04 15:22:01 -07:00

90 lines
2.3 KiB
CoffeeScript

React = require 'react'
classNames = require 'classnames'
MessageItem = require './message-item'
{Utils,
DraftStore,
ComponentRegistry,
MessageStore} = require 'nylas-exports'
class MessageItemContainer extends React.Component
@displayName = 'MessageItemContainer'
@propTypes =
thread: React.PropTypes.object.isRequired
message: React.PropTypes.object.isRequired
collapsed: React.PropTypes.bool
isLastMsg: React.PropTypes.bool
isBeforeReplyArea: React.PropTypes.bool
scrollTo: React.PropTypes.func
constructor: (@props) ->
@state = @_getStateFromStores()
componentWillReceiveProps: (newProps) ->
@setState(@_getStateFromStores(newProps))
componentDidMount: =>
if @props.message.draft
@_unlisten = DraftStore.listen @_onSendingStateChanged
shouldComponentUpdate: (nextProps, nextState) =>
not Utils.isEqualReact(nextProps, @props) or
not Utils.isEqualReact(nextState, @state)
componentWillUnmount: =>
@_unlisten() if @_unlisten
focus: =>
@refs.message.focus()
render: =>
if @props.message.draft
if @state.isSending
@_renderMessage(pending: true)
else
@_renderComposer()
else
@_renderMessage(pending: false)
_renderMessage: ({pending}) =>
<MessageItem
ref="message"
pending={pending}
thread={@props.thread}
message={@props.message}
className={@_classNames()}
collapsed={@props.collapsed}
isLastMsg={@props.isLastMsg} />
_renderComposer: =>
Composer = ComponentRegistry.findComponentsMatching(role: 'Composer')[0]
if (!Composer)
return <span></span>
<Composer
ref="message"
draftClientId={@props.message.clientId}
className={@_classNames()}
mode={"inline"}
threadId={@props.thread.id}
scrollTo={@props.scrollTo}
/>
_classNames: => classNames
"draft": @props.message.draft
"unread": @props.message.unread
"collapsed": @props.collapsed
"message-item-wrap": true
"before-reply-area": @props.isBeforeReplyArea
_onSendingStateChanged: (draftClientId) =>
if draftClientId is @props.message.clientId
@setState(@_getStateFromStores())
_getStateFromStores: (props = @props) ->
isSending: DraftStore.isSendingDraft(props.message.clientId)
module.exports = MessageItemContainer