From 3195ba5d51936da5ee4595276677db43949dd442 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 20 Jan 2016 20:09:30 -0800 Subject: [PATCH] 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 --- .../contenteditable/contenteditable.cjsx | 19 +++++++++++++------ .../contenteditable/floating-toolbar.cjsx | 16 +++++++++------- .../contenteditable/link-editor.cjsx | 3 +++ .../contenteditable/link-manager.coffee | 16 +++++++++++----- .../contenteditable/mouse-service.coffee | 3 +++ src/window-event-handler.coffee | 3 +++ 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/components/contenteditable/contenteditable.cjsx b/src/components/contenteditable/contenteditable.cjsx index 304f53c6b..1f24d7727 100644 --- a/src/components/contenteditable/contenteditable.cjsx +++ b/src/components/contenteditable/contenteditable.cjsx @@ -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()} -
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()) diff --git a/src/components/contenteditable/floating-toolbar.cjsx b/src/components/contenteditable/floating-toolbar.cjsx index 4db991c07..9fc5de4a5 100644 --- a/src/components/contenteditable/floating-toolbar.cjsx +++ b/src/components/contenteditable/floating-toolbar.cjsx @@ -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 diff --git a/src/components/contenteditable/link-editor.cjsx b/src/components/contenteditable/link-editor.cjsx index b7e31d5bd..66df22646 100644 --- a/src/components/contenteditable/link-editor.cjsx +++ b/src/components/contenteditable/link-editor.cjsx @@ -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() diff --git a/src/components/contenteditable/link-manager.coffee b/src/components/contenteditable/link-manager.coffee index 1e59f7db1..ef8c6b126 100644 --- a/src/components/contenteditable/link-manager.coffee +++ b/src/components/contenteditable/link-manager.coffee @@ -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() diff --git a/src/components/contenteditable/mouse-service.coffee b/src/components/contenteditable/mouse-service.coffee index 0af3000c6..f5b1aa81f 100644 --- a/src/components/contenteditable/mouse-service.coffee +++ b/src/components/contenteditable/mouse-service.coffee @@ -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 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 diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 7a768a949..e2fc6ea57 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -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