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:
Evan Morikawa 2016-01-20 20:09:30 -08:00
parent 36012f84f2
commit 3195ba5d51
6 changed files with 42 additions and 18 deletions

View file

@ -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())

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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