mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
fix(composer): don't blur when adding a link
Summary: Refactor focusing behavior in floating toolbar controller Fixes T3781 Fixes T3791 Test Plan: manual Reviewers: bengotow Reviewed By: bengotow Maniphest Tasks: T3791, T3781 Differential Revision: https://phab.nylas.com/D2073
This commit is contained in:
parent
fc83f3cfab
commit
8760de1b0e
|
@ -153,7 +153,7 @@ class ComposerView extends React.Component
|
|||
|
||||
render: =>
|
||||
if @props.mode is "inline"
|
||||
<FocusTrackingRegion className={@_wrapClasses()} onFocus={@focus} tabIndex="-1">
|
||||
<FocusTrackingRegion className={@_wrapClasses()} tabIndex="-1">
|
||||
{@_renderComposer()}
|
||||
</FocusTrackingRegion>
|
||||
else
|
||||
|
|
|
@ -230,14 +230,23 @@ class ContenteditableComponent extends React.Component
|
|||
else
|
||||
document.execCommand("outdent")
|
||||
|
||||
# The native document.execCommand('outdent')
|
||||
_outdent: ->
|
||||
|
||||
_closestAtCursor: (selector) ->
|
||||
selection = document.getSelection()
|
||||
return unless selection?.isCollapsed
|
||||
return selection.anchorNode?.closest(selector)
|
||||
return @_closest(selection.anchorNode, selector)
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
|
||||
# Only Elements (not Text nodes) have the `closest` method
|
||||
_closest: (node, selector) ->
|
||||
el = if node instanceof HTMLElement then node else node.parentElement
|
||||
return el.closest(selector)
|
||||
|
||||
_replaceFirstListItem: (li, replaceWith) ->
|
||||
@_teardownSelectionListeners()
|
||||
list = li.closest("ul, ol")
|
||||
list = @_closest(li, "ul, ol")
|
||||
|
||||
if replaceWith.length is 0
|
||||
replaceWith = replaceWith.replace /\s/g, " "
|
||||
|
@ -269,19 +278,19 @@ class ContenteditableComponent extends React.Component
|
|||
event.preventDefault()
|
||||
selection = document.getSelection()
|
||||
if selection?.isCollapsed
|
||||
# https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
|
||||
if selection.anchorNode instanceof HTMLElement
|
||||
anchorElement = selection.anchorNode
|
||||
else
|
||||
anchorElement = selection.anchorNode.parentElement
|
||||
|
||||
# Only Elements (not Text nodes) have the `closest` method
|
||||
if anchorElement.closest("li")
|
||||
li = @_closestAtCursor("li")
|
||||
if li
|
||||
if event.shiftKey
|
||||
document.execCommand("outdent")
|
||||
list = @_closestAtCursor("ul, ol")
|
||||
# BUG: As of 9/25/15 if you outdent the first item in a list, it
|
||||
# doesn't work :(
|
||||
if list.querySelectorAll('li')?[0] is li # We're in first li
|
||||
@_replaceFirstListItem(li, li.innerHTML)
|
||||
else
|
||||
document.execCommand("outdent")
|
||||
else
|
||||
document.execCommand("indent")
|
||||
return
|
||||
else if event.shiftKey
|
||||
if @_atTabChar()
|
||||
@_removeLastCharacter()
|
||||
|
@ -312,7 +321,7 @@ class ContenteditableComponent extends React.Component
|
|||
return false if not selection.isCollapsed
|
||||
return true if anchor?.nodeName is "LI"
|
||||
return false if selection.anchorOffset > 0
|
||||
li = anchor.closest("li")
|
||||
li = @_closest(anchor, "li")
|
||||
return unless li
|
||||
return DOMUtils.isFirstChild(li, anchor)
|
||||
|
||||
|
@ -449,8 +458,6 @@ class ContenteditableComponent extends React.Component
|
|||
|
||||
_onBlur: (event) =>
|
||||
@setInnerState dragging: false
|
||||
# The delay here is necessary to see if the blur was caused by us
|
||||
# navigating to the toolbar and focusing on the set-url input.
|
||||
return if @_editableNode().parentElement.contains event.relatedTarget
|
||||
@setInnerState editableFocused: false
|
||||
|
||||
|
@ -848,6 +855,8 @@ class ContenteditableComponent extends React.Component
|
|||
else @_execCommand ["createLink", false, url]
|
||||
@_restoreSelection(force: true, collapse: "end")
|
||||
|
||||
return
|
||||
|
||||
_execCommand: (commandArgs=[], selectionRange={}) =>
|
||||
{anchorNode, anchorOffset, focusNode, focusOffset} = selectionRange
|
||||
@_teardownSelectionListeners()
|
||||
|
|
|
@ -91,21 +91,31 @@ class FloatingToolbarContainer extends React.Component
|
|||
onDomMutator={@props.onDomMutator}
|
||||
linkToModify={@state.linkToModify}
|
||||
onChangeFocus={@_onChangeFocus}
|
||||
editAreaWidth={@state.editAreaWidth}
|
||||
contentPadding={@CONTENT_PADDING}
|
||||
editAreaWidth={@state.editAreaWidth} />
|
||||
onDoneWithLink={@_onDoneWithLink}
|
||||
onClickLinkEditBtn={@_onClickLinkEditBtn} />
|
||||
|
||||
# Called when a user clicks the "link" icon on the FloatingToolbar
|
||||
_onClickLinkEditBtn: =>
|
||||
@setState toolbarMode: "edit-link"
|
||||
|
||||
# A user could be done with a link because they're setting a new one, or
|
||||
# clearing one, or just canceling.
|
||||
_onDoneWithLink: =>
|
||||
@componentWillReceiveInnerProps linkHoveringOver: null
|
||||
@setState
|
||||
toolbarMode: "buttons"
|
||||
toolbarVisible: false
|
||||
return
|
||||
|
||||
# We explicitly control the focus of the FloatingToolbar because we can
|
||||
# do things like switch from "buttons" mode to "edit-link" mode (which
|
||||
# natively fires focus change events) but not want to signify a "focus"
|
||||
# change
|
||||
_onChangeFocus: (focus) =>
|
||||
@componentWillReceiveInnerProps toolbarFocus: focus
|
||||
|
||||
_onChangeMode: (mode) =>
|
||||
if mode is "buttons"
|
||||
@componentWillReceiveInnerProps linkHoveringOver: null
|
||||
@setState
|
||||
toolbarMode: mode
|
||||
toolbarVisible: false
|
||||
else
|
||||
@setState toolbarMode: mode
|
||||
|
||||
# We want the toolbar's state to be declaratively defined from other
|
||||
# states.
|
||||
_setToolbarState: =>
|
||||
|
|
|
@ -86,7 +86,7 @@ class FloatingToolbar extends React.Component
|
|||
onClick={@_execCommand}
|
||||
data-command-name="underline"></button>
|
||||
<button className="btn btn-link toolbar-btn"
|
||||
onClick={@_showLink}
|
||||
onClick={@props.onClickLinkEditBtn}
|
||||
data-command-name="link"></button>
|
||||
{@_toolbarExtensions()}
|
||||
</div>
|
||||
|
@ -164,7 +164,7 @@ class FloatingToolbar extends React.Component
|
|||
_removeUrl: =>
|
||||
@setState urlInputValue: ""
|
||||
@props.onSaveUrl "", @props.linkToModify
|
||||
@props.onChangeMode("buttons")
|
||||
@props.onDoneWithLink()
|
||||
|
||||
_onFocus: =>
|
||||
@props.onChangeFocus(true)
|
||||
|
@ -188,7 +188,7 @@ class FloatingToolbar extends React.Component
|
|||
_saveUrl: =>
|
||||
if (@state.urlInputValue ? "").trim().length > 0
|
||||
@props.onSaveUrl @state.urlInputValue, @props.linkToModify
|
||||
@props.onChangeMode("buttons")
|
||||
@props.onDoneWithLink()
|
||||
|
||||
_execCommand: (event) =>
|
||||
cmd = event.currentTarget.getAttribute 'data-command-name'
|
||||
|
@ -239,7 +239,4 @@ class FloatingToolbar extends React.Component
|
|||
else
|
||||
return TOOLBAR_BUTTONS_WIDTH
|
||||
|
||||
_showLink: =>
|
||||
@props.onChangeMode("edit-link")
|
||||
|
||||
module.exports = FloatingToolbar
|
||||
|
|
|
@ -24,6 +24,19 @@ class FocusTrackingRegion extends React.Component
|
|||
@_goingout = true
|
||||
setTimeout =>
|
||||
return unless @_goingout
|
||||
|
||||
# If we're unmounted the `@_goingout` flag will catch the unmount
|
||||
# @_goingout is set to true when we umount
|
||||
#
|
||||
# It's posible for a focusout event to fire from within a region
|
||||
# that we're actually focsued on.
|
||||
#
|
||||
# This happens when component that used to have the focus is
|
||||
# unmounted. An example is the url input field of the
|
||||
# FloatingToolbar in the Composer's Contenteditable
|
||||
el = React.findDOMNode(@)
|
||||
return if el.contains document.activeElement
|
||||
|
||||
# This prevents the strange effect of an input appearing to have focus
|
||||
# when the element receiving focus does not support selection (like a
|
||||
# div with tabIndex=-1)
|
||||
|
|
Loading…
Reference in a new issue