mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 15:56:10 +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: =>
|
render: =>
|
||||||
if @props.mode is "inline"
|
if @props.mode is "inline"
|
||||||
<FocusTrackingRegion className={@_wrapClasses()} onFocus={@focus} tabIndex="-1">
|
<FocusTrackingRegion className={@_wrapClasses()} tabIndex="-1">
|
||||||
{@_renderComposer()}
|
{@_renderComposer()}
|
||||||
</FocusTrackingRegion>
|
</FocusTrackingRegion>
|
||||||
else
|
else
|
||||||
|
|
|
@ -230,14 +230,23 @@ class ContenteditableComponent extends React.Component
|
||||||
else
|
else
|
||||||
document.execCommand("outdent")
|
document.execCommand("outdent")
|
||||||
|
|
||||||
|
# The native document.execCommand('outdent')
|
||||||
|
_outdent: ->
|
||||||
|
|
||||||
_closestAtCursor: (selector) ->
|
_closestAtCursor: (selector) ->
|
||||||
selection = document.getSelection()
|
selection = document.getSelection()
|
||||||
return unless selection?.isCollapsed
|
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) ->
|
_replaceFirstListItem: (li, replaceWith) ->
|
||||||
@_teardownSelectionListeners()
|
@_teardownSelectionListeners()
|
||||||
list = li.closest("ul, ol")
|
list = @_closest(li, "ul, ol")
|
||||||
|
|
||||||
if replaceWith.length is 0
|
if replaceWith.length is 0
|
||||||
replaceWith = replaceWith.replace /\s/g, " "
|
replaceWith = replaceWith.replace /\s/g, " "
|
||||||
|
@ -269,19 +278,19 @@ class ContenteditableComponent extends React.Component
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
selection = document.getSelection()
|
selection = document.getSelection()
|
||||||
if selection?.isCollapsed
|
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
|
# Only Elements (not Text nodes) have the `closest` method
|
||||||
if anchorElement.closest("li")
|
li = @_closestAtCursor("li")
|
||||||
|
if li
|
||||||
if event.shiftKey
|
if event.shiftKey
|
||||||
|
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")
|
document.execCommand("outdent")
|
||||||
else
|
else
|
||||||
document.execCommand("indent")
|
document.execCommand("indent")
|
||||||
return
|
|
||||||
else if event.shiftKey
|
else if event.shiftKey
|
||||||
if @_atTabChar()
|
if @_atTabChar()
|
||||||
@_removeLastCharacter()
|
@_removeLastCharacter()
|
||||||
|
@ -312,7 +321,7 @@ class ContenteditableComponent extends React.Component
|
||||||
return false if not selection.isCollapsed
|
return false if not selection.isCollapsed
|
||||||
return true if anchor?.nodeName is "LI"
|
return true if anchor?.nodeName is "LI"
|
||||||
return false if selection.anchorOffset > 0
|
return false if selection.anchorOffset > 0
|
||||||
li = anchor.closest("li")
|
li = @_closest(anchor, "li")
|
||||||
return unless li
|
return unless li
|
||||||
return DOMUtils.isFirstChild(li, anchor)
|
return DOMUtils.isFirstChild(li, anchor)
|
||||||
|
|
||||||
|
@ -449,8 +458,6 @@ class ContenteditableComponent extends React.Component
|
||||||
|
|
||||||
_onBlur: (event) =>
|
_onBlur: (event) =>
|
||||||
@setInnerState dragging: false
|
@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
|
return if @_editableNode().parentElement.contains event.relatedTarget
|
||||||
@setInnerState editableFocused: false
|
@setInnerState editableFocused: false
|
||||||
|
|
||||||
|
@ -848,6 +855,8 @@ class ContenteditableComponent extends React.Component
|
||||||
else @_execCommand ["createLink", false, url]
|
else @_execCommand ["createLink", false, url]
|
||||||
@_restoreSelection(force: true, collapse: "end")
|
@_restoreSelection(force: true, collapse: "end")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
_execCommand: (commandArgs=[], selectionRange={}) =>
|
_execCommand: (commandArgs=[], selectionRange={}) =>
|
||||||
{anchorNode, anchorOffset, focusNode, focusOffset} = selectionRange
|
{anchorNode, anchorOffset, focusNode, focusOffset} = selectionRange
|
||||||
@_teardownSelectionListeners()
|
@_teardownSelectionListeners()
|
||||||
|
|
|
@ -91,20 +91,30 @@ class FloatingToolbarContainer extends React.Component
|
||||||
onDomMutator={@props.onDomMutator}
|
onDomMutator={@props.onDomMutator}
|
||||||
linkToModify={@state.linkToModify}
|
linkToModify={@state.linkToModify}
|
||||||
onChangeFocus={@_onChangeFocus}
|
onChangeFocus={@_onChangeFocus}
|
||||||
|
editAreaWidth={@state.editAreaWidth}
|
||||||
contentPadding={@CONTENT_PADDING}
|
contentPadding={@CONTENT_PADDING}
|
||||||
editAreaWidth={@state.editAreaWidth} />
|
onDoneWithLink={@_onDoneWithLink}
|
||||||
|
onClickLinkEditBtn={@_onClickLinkEditBtn} />
|
||||||
|
|
||||||
_onChangeFocus: (focus) =>
|
# Called when a user clicks the "link" icon on the FloatingToolbar
|
||||||
@componentWillReceiveInnerProps toolbarFocus: focus
|
_onClickLinkEditBtn: =>
|
||||||
|
@setState toolbarMode: "edit-link"
|
||||||
|
|
||||||
_onChangeMode: (mode) =>
|
# A user could be done with a link because they're setting a new one, or
|
||||||
if mode is "buttons"
|
# clearing one, or just canceling.
|
||||||
|
_onDoneWithLink: =>
|
||||||
@componentWillReceiveInnerProps linkHoveringOver: null
|
@componentWillReceiveInnerProps linkHoveringOver: null
|
||||||
@setState
|
@setState
|
||||||
toolbarMode: mode
|
toolbarMode: "buttons"
|
||||||
toolbarVisible: false
|
toolbarVisible: false
|
||||||
else
|
return
|
||||||
@setState toolbarMode: mode
|
|
||||||
|
# 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
|
||||||
|
|
||||||
# We want the toolbar's state to be declaratively defined from other
|
# We want the toolbar's state to be declaratively defined from other
|
||||||
# states.
|
# states.
|
||||||
|
|
|
@ -86,7 +86,7 @@ class FloatingToolbar extends React.Component
|
||||||
onClick={@_execCommand}
|
onClick={@_execCommand}
|
||||||
data-command-name="underline"></button>
|
data-command-name="underline"></button>
|
||||||
<button className="btn btn-link toolbar-btn"
|
<button className="btn btn-link toolbar-btn"
|
||||||
onClick={@_showLink}
|
onClick={@props.onClickLinkEditBtn}
|
||||||
data-command-name="link"></button>
|
data-command-name="link"></button>
|
||||||
{@_toolbarExtensions()}
|
{@_toolbarExtensions()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,7 +164,7 @@ class FloatingToolbar extends React.Component
|
||||||
_removeUrl: =>
|
_removeUrl: =>
|
||||||
@setState urlInputValue: ""
|
@setState urlInputValue: ""
|
||||||
@props.onSaveUrl "", @props.linkToModify
|
@props.onSaveUrl "", @props.linkToModify
|
||||||
@props.onChangeMode("buttons")
|
@props.onDoneWithLink()
|
||||||
|
|
||||||
_onFocus: =>
|
_onFocus: =>
|
||||||
@props.onChangeFocus(true)
|
@props.onChangeFocus(true)
|
||||||
|
@ -188,7 +188,7 @@ class FloatingToolbar extends React.Component
|
||||||
_saveUrl: =>
|
_saveUrl: =>
|
||||||
if (@state.urlInputValue ? "").trim().length > 0
|
if (@state.urlInputValue ? "").trim().length > 0
|
||||||
@props.onSaveUrl @state.urlInputValue, @props.linkToModify
|
@props.onSaveUrl @state.urlInputValue, @props.linkToModify
|
||||||
@props.onChangeMode("buttons")
|
@props.onDoneWithLink()
|
||||||
|
|
||||||
_execCommand: (event) =>
|
_execCommand: (event) =>
|
||||||
cmd = event.currentTarget.getAttribute 'data-command-name'
|
cmd = event.currentTarget.getAttribute 'data-command-name'
|
||||||
|
@ -239,7 +239,4 @@ class FloatingToolbar extends React.Component
|
||||||
else
|
else
|
||||||
return TOOLBAR_BUTTONS_WIDTH
|
return TOOLBAR_BUTTONS_WIDTH
|
||||||
|
|
||||||
_showLink: =>
|
|
||||||
@props.onChangeMode("edit-link")
|
|
||||||
|
|
||||||
module.exports = FloatingToolbar
|
module.exports = FloatingToolbar
|
||||||
|
|
|
@ -24,6 +24,19 @@ class FocusTrackingRegion extends React.Component
|
||||||
@_goingout = true
|
@_goingout = true
|
||||||
setTimeout =>
|
setTimeout =>
|
||||||
return unless @_goingout
|
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
|
# This prevents the strange effect of an input appearing to have focus
|
||||||
# when the element receiving focus does not support selection (like a
|
# when the element receiving focus does not support selection (like a
|
||||||
# div with tabIndex=-1)
|
# div with tabIndex=-1)
|
||||||
|
|
Loading…
Reference in a new issue