mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 13:14:16 +08:00
fix(link): fix link focus and UX issues
Fix FloatingToolbar position Fix link-editor to refresh on new props Fix link content changed Fix link width
This commit is contained in:
parent
36012f84f2
commit
3195ba5d51
6 changed files with 42 additions and 18 deletions
|
@ -100,7 +100,7 @@ class Contenteditable extends React.Component
|
|||
editor = new EditorAPI(@_editableNode())
|
||||
|
||||
if not editor.currentSelection().isInScope()
|
||||
editor.importSelection(@innerState.exportedSelection)
|
||||
@_restoreSelection()
|
||||
|
||||
argsObj = _.extend(extraArgsObj, {editor})
|
||||
|
||||
|
@ -152,7 +152,7 @@ class Contenteditable extends React.Component
|
|||
previousExportedSelection: @innerState.exportedSelection
|
||||
|
||||
componentDidUpdate: =>
|
||||
@_restoreSelection()
|
||||
@_restoreSelection() if @_shouldRestoreSelectionOnUpdate()
|
||||
@_refreshServices()
|
||||
@_mutationObserver.disconnect()
|
||||
@_mutationObserver.observe(@_editableNode(), @_mutationConfig())
|
||||
|
@ -192,7 +192,7 @@ class Contenteditable extends React.Component
|
|||
localHandlers={@_keymapHandlers()}>
|
||||
{@_renderFloatingToolbar()}
|
||||
|
||||
<div className="contenteditable"
|
||||
<div className="contenteditable no-open-link-events"
|
||||
ref="contenteditable"
|
||||
contentEditable
|
||||
spellCheck={false}
|
||||
|
@ -493,6 +493,7 @@ class Contenteditable extends React.Component
|
|||
# you revert to a previous state, the selection updates as well.
|
||||
_saveSelection: =>
|
||||
selection = new ExtendedSelection(@_editableNode())
|
||||
console.trace()
|
||||
return unless selection?.isInScope()
|
||||
|
||||
@setInnerState
|
||||
|
@ -501,7 +502,6 @@ class Contenteditable extends React.Component
|
|||
previousExportedSelection: @innerState.exportedSelection
|
||||
|
||||
_restoreSelection: =>
|
||||
return unless @_shouldRestoreSelection()
|
||||
@_teardownListeners()
|
||||
selection = new ExtendedSelection(@_editableNode())
|
||||
selection.importSelection(@innerState.exportedSelection)
|
||||
|
@ -509,9 +509,16 @@ class Contenteditable extends React.Component
|
|||
@_onSelectionChanged(selection)
|
||||
@_setupListeners()
|
||||
|
||||
_shouldRestoreSelection: ->
|
||||
# When the component updates, the selection may have changed from our
|
||||
# last known saved position. This can happen for a couple of reasons:
|
||||
#
|
||||
# 1. Some sister-component (like the LinkEditor) grabbed the selection.
|
||||
# 2. A sister-component that used to have the selection was unmounted
|
||||
# causing the selection to be null or the document
|
||||
_shouldRestoreSelectionOnUpdate: ->
|
||||
(not @innerState.dragging) and
|
||||
document.activeElement is @_editableNode()
|
||||
(document.activeElement is @_editableNode() or
|
||||
not @_editableNode().parentNode.contains(document.activeElement))
|
||||
|
||||
_onSelectionChanged: (selection) ->
|
||||
@props.onSelectionChanged(selection, @_editableNode())
|
||||
|
|
|
@ -102,12 +102,11 @@ class FloatingToolbar extends React.Component
|
|||
|
||||
_getStateFromProps: (props) ->
|
||||
toolbarComponentState = @_getToolbarComponentData(props)
|
||||
locationRefNode = toolbarComponentState.toolbarLocationRef
|
||||
if locationRefNode
|
||||
positionState = @_calculatePositionState(props, locationRefNode)
|
||||
if toolbarComponentState.toolbarLocationRef
|
||||
positionState = @_calculatePositionState(props, toolbarComponentState)
|
||||
else positionState = {}
|
||||
|
||||
return _.extend {}, positionState, toolbarComponentState
|
||||
return _.extend {}, toolbarComponentState, positionState
|
||||
|
||||
# If this returns a `null` component, that means we don't want to show
|
||||
# anything.
|
||||
|
@ -135,10 +134,10 @@ class FloatingToolbar extends React.Component
|
|||
|
||||
@CONTENT_PADDING: 15
|
||||
|
||||
_calculatePositionState: (props, locationRefNode) =>
|
||||
_calculatePositionState: (props, {toolbarLocationRef, toolbarWidth}) =>
|
||||
editableNode = props.editableNode
|
||||
|
||||
referenceRect = locationRefNode.getBoundingClientRect()
|
||||
referenceRect = toolbarLocationRef.getBoundingClientRect()
|
||||
|
||||
if not editableNode or not referenceRect or DOMUtils.isEmptyBoundingRect(referenceRect)
|
||||
return {toolbarTop: 0, toolbarLeft: 0, editAreaWidth: 0, toolbarPos: 'above'}
|
||||
|
@ -158,9 +157,12 @@ class FloatingToolbar extends React.Component
|
|||
calcTop = referenceRect.top - editArea.top + referenceRect.height + TOP_PADDING + 4
|
||||
toolbarPos = "below"
|
||||
|
||||
maxWidth = editArea.width - FloatingToolbar.CONTENT_PADDING * 2
|
||||
|
||||
return {
|
||||
toolbarTop: calcTop
|
||||
toolbarLeft: calcLeft
|
||||
toolbarWidth: Math.min(maxWidth, toolbarWidth)
|
||||
editAreaWidth: editArea.width
|
||||
toolbarPos: toolbarPos
|
||||
}
|
||||
|
@ -180,7 +182,7 @@ class FloatingToolbar extends React.Component
|
|||
return styles
|
||||
|
||||
_toolbarLeft: =>
|
||||
max = @state.editAreaWidth - @state.toolbarWidth - FloatingToolbar.CONTENT_PADDING
|
||||
max = Math.max(@state.editAreaWidth - @state.toolbarWidth - FloatingToolbar.CONTENT_PADDING, FloatingToolbar.CONTENT_PADDING)
|
||||
left = Math.min(Math.max(@state.toolbarLeft - @state.toolbarWidth/2, FloatingToolbar.CONTENT_PADDING), max)
|
||||
return left
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ class LinkEditor extends React.Component
|
|||
@state =
|
||||
urlInputValue: @_initialUrl() ? ""
|
||||
|
||||
componentWillReceiveProps: (newProps) ->
|
||||
@setState urlInputValue: @_initialUrl(newProps)
|
||||
|
||||
componentDidMount: ->
|
||||
if @props.focusOnMount
|
||||
React.findDOMNode(@refs["urlInput"]).focus()
|
||||
|
|
|
@ -20,7 +20,7 @@ class LinkManager extends ContenteditableExtension
|
|||
# something new.
|
||||
@onContentChanged: ({editor, mutations}) =>
|
||||
sel = editor.currentSelection()
|
||||
if sel.isCollapsed
|
||||
if sel.anchorNode and sel.isCollapsed
|
||||
node = sel.anchorNode
|
||||
sibling = node.previousSibling
|
||||
|
||||
|
@ -31,6 +31,7 @@ class LinkManager extends ContenteditableExtension
|
|||
return if /^\s+/.test(node.data)
|
||||
return if RegExpUtils.punctuation(exclude: ['\\-', '_']).test(node.data[0])
|
||||
|
||||
node.splitText(1) if node.data.length > 1
|
||||
sibling.appendChild(node)
|
||||
sibling.normalize()
|
||||
text = DOMUtils.findLastTextNode(sibling)
|
||||
|
@ -63,8 +64,8 @@ class LinkManager extends ContenteditableExtension
|
|||
|
||||
@_linkWidth: (linkToModify) ->
|
||||
href = linkToModify?.getAttribute?('href') ? ""
|
||||
WIDTH_PER_CHAR = 11
|
||||
return Math.max(href.length * WIDTH_PER_CHAR, 210)
|
||||
WIDTH_PER_CHAR = 8
|
||||
return Math.max(href.length * WIDTH_PER_CHAR + 95, 210)
|
||||
|
||||
@_linkAtCursor: (toolbarState) ->
|
||||
if toolbarState.selectionSnapshot.isCollapsed
|
||||
|
@ -73,7 +74,12 @@ class LinkManager extends ContenteditableExtension
|
|||
else
|
||||
anchor = toolbarState.selectionSnapshot.anchorNode
|
||||
focus = toolbarState.selectionSnapshot.anchorNode
|
||||
return DOMUtils.closest(anchor, 'n1-prompt-link') and DOMUtils.closest(focus, 'n1-prompt-link')
|
||||
aPrompt = DOMUtils.closest(anchor, 'n1-prompt-link')
|
||||
fPrompt = DOMUtils.closest(focus, 'n1-prompt-link')
|
||||
if aPrompt and fPrompt and aPrompt is fPrompt
|
||||
aTag = DOMUtils.closest(aPrompt, 'a')
|
||||
return aTag ? aPrompt
|
||||
else return null
|
||||
|
||||
## TODO FIXME: Unfortunately, the keyCommandHandler fires before the
|
||||
# Contentedtiable onKeyDown.
|
||||
|
@ -107,7 +113,7 @@ class LinkManager extends ContenteditableExtension
|
|||
|
||||
@_onSaveUrl: ({editor, url, linkToModify}) ->
|
||||
if linkToModify?
|
||||
equivalentNode = DOMUtils.findSimilarNodesAtIndex(editor.rootNode, linkToModify, 0)?[0]
|
||||
equivalentNode = DOMUtils.findSimilarNodeAtIndex(editor.rootNode, linkToModify, 0)
|
||||
return unless equivalentNode?
|
||||
equivalentLinkText = DOMUtils.findFirstTextNode(equivalentNode)
|
||||
return if linkToModify.getAttribute?('href')?.trim() is url.trim()
|
||||
|
|
|
@ -22,6 +22,9 @@ class MouseService extends ContenteditableService
|
|||
# Note: Related to composer-view#_onClickComposeBody
|
||||
event.stopPropagation()
|
||||
|
||||
## NOTE: We can't use event.preventDefault() here for <a> tags because
|
||||
# the window-event-handler.coffee file has already caught the event.
|
||||
|
||||
# We use global listeners to determine whether or not dragging is
|
||||
# happening. This is because dragging may stop outside the scope of
|
||||
# this element. Note that the `dragstart` and `dragend` events don't
|
||||
|
|
|
@ -149,6 +149,9 @@ class WindowEventHandler
|
|||
href = target?.getAttribute('href') or currentTarget?.getAttribute('href')
|
||||
|
||||
return unless href
|
||||
|
||||
return if currentTarget.closest('.no-open-link-events')
|
||||
|
||||
schema = url.parse(href).protocol
|
||||
return unless schema
|
||||
|
||||
|
|
Loading…
Reference in a new issue