From 1d99a18bf631b412486cd1111b290d9bee47009a Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Thu, 19 Mar 2015 17:16:38 -0700 Subject: [PATCH] feat(composer): floating toolbar now fades in Summary: tooltip fades in now Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://review.inboxapp.com/D1317 --- .../lib/contenteditable-component.cjsx | 56 ++++++++++++++----- .../composer/lib/floating-toolbar.cjsx | 25 ++++++--- .../composer/stylesheets/composer.less | 13 +++++ .../thread-list/lib/thread-list.cjsx | 2 +- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/internal_packages/composer/lib/contenteditable-component.cjsx b/internal_packages/composer/lib/contenteditable-component.cjsx index 0e9df01bf..7e0b3b5df 100644 --- a/internal_packages/composer/lib/contenteditable-component.cjsx +++ b/internal_packages/composer/lib/contenteditable-component.cjsx @@ -29,10 +29,12 @@ ContenteditableComponent = React.createClass componentDidMount: -> @_setupSelectionListeners() @_setupLinkHoverListeners() + @_setupGlobalMouseListener() componentWillUnmount: -> @_teardownSelectionListeners() @_teardownLinkHoverListeners() + @_teardownGlobalMouseListener() componentWillReceiveProps: (nextProps) -> if nextProps.initialSelectionSnapshot? @@ -69,8 +71,6 @@ ContenteditableComponent = React.createClass onBlur={@_onBlur} onPaste={@_onPaste} onInput={@_onInput} - onMouseUp={@_onMouseUp} - onMouseDown={@_onMouseDown} dangerouslySetInnerHTML={@_dangerouslySetInnerHTML()}> @@ -79,7 +79,7 @@ ContenteditableComponent = React.createClass @_editableNode().focus() if @isMounted() _onInput: (event) -> - @_ignoreSelectionRestoration = false + @_dragging = false @_editableNode().normalize() @_setNewSelectionState() html = @_unapplyHTMLDisplayFilters(@_editableNode().innerHTML) @@ -212,17 +212,46 @@ ContenteditableComponent = React.createClass @_previousSelection = @_selection @_selection = selection - # When we're dragging we don't want to the restoring the cursor as we're - # dragging. Doing so caused selecting backwards to break because the - # Selection API does not yet expose the selection "direction". When we - # would go to reset the cursor selection, it would reset to the wrong - # state. + + # 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 + # detect text selection. They are for drag & drop. + _setupGlobalMouseListener: -> + @__onMouseDown = _.bind(@_onMouseDown, @) + @__onMouseMove = _.bind(@_onMouseMove, @) + @__onMouseUp = _.bind(@_onMouseUp, @) + window.addEventListener("mousedown", @__onMouseDown) + window.addEventListener("mouseup", @__onMouseUp) + _teardownGlobalMouseListener: -> + window.removeEventListener("mousedown", @__onMouseDown) + window.removeEventListener("mouseup", @__onMouseUp) + _onMouseDown: (event) -> - @_ignoreSelectionRestoration = true - return event + @_mouseDownEvent = event + @_mouseHasMoved = false + window.addEventListener("mousemove", @__onMouseMove) + _onMouseMove: (event) -> + if not @_mouseHasMoved + @_onDragStart(@_mouseDownEvent) + @_mouseHasMoved = true _onMouseUp: (event) -> - @_ignoreSelectionRestoration = false - return event + window.removeEventListener("mousemove", @__onMouseMove) + if @_mouseHasMoved + @_mouseHasMoved = false + @_onDragEnd(event) + + _onDragStart: (event) -> + return unless @isMounted() + editable = @refs.contenteditable.getDOMNode() + if editable is event.target or editable.contains(event.target) + @_dragging = true + + _onDragEnd: (event) -> + return unless @isMounted() + if @_dragging + @_dragging = false + @_refreshToolbarState() # We manually restore the selection on every render and when we need to # move the selection around manually. @@ -234,7 +263,7 @@ ContenteditableComponent = React.createClass # selection, we'll collapse the range into a single caret # position _restoreSelection: ({force, collapse}={}) -> - return if @_ignoreSelectionRestoration + return if @_dragging return if not @_selection? return if document.activeElement isnt @_editableNode() and not force return if not @_selection.startNode? or not @_selection.endNode? @@ -354,6 +383,7 @@ ContenteditableComponent = React.createClass # 2. When you've arrow-keyed the cursor into a link # 3. When you have selected a range of text. _refreshToolbarState: -> + return if @_dragging if @_linkHoveringOver url = @_linkHoveringOver.getAttribute('href') rect = @_linkHoveringOver.getBoundingClientRect() diff --git a/internal_packages/composer/lib/floating-toolbar.cjsx b/internal_packages/composer/lib/floating-toolbar.cjsx index c4e5b11d2..37636b6ef 100644 --- a/internal_packages/composer/lib/floating-toolbar.cjsx +++ b/internal_packages/composer/lib/floating-toolbar.cjsx @@ -1,5 +1,5 @@ _ = require 'underscore-plus' -React = require 'react' +React = require 'react/addons' {CompositeDisposable} = require 'event-kit' {RetinaImg} = require 'ui-components' @@ -32,11 +32,25 @@ FloatingToolbar = React.createClass render: ->
+ className={@_toolbarClasses()} style={@_toolbarStyles()}>
{@_toolbarType()}
+ _toolbarClasses: -> + classes = {} + classes[@props.pos] = true + React.addons.classSet _.extend classes, + "floating-toolbar": true + "toolbar": true + "toolbar-visible": @props.visible + + _toolbarStyles: -> + styles = + left: @_toolbarLeft() + top: @props.top + return styles + _toolbarType: -> if @state.mode is "buttons" then @_renderButtons() else if @state.mode is "edit-link" then @_renderLink() @@ -118,13 +132,6 @@ FloatingToolbar = React.createClass document.execCommand(cmd, false, null) true - _toolbarStyles: -> - styles = - left: @_toolbarLeft() - top: @props.top - display: if @props.visible then "block" else "none" - return styles - _toolbarLeft: -> CONTENT_PADDING = @props.contentPadding ? 15 max = @props.editAreaWidth - @_halfWidth()*2 - CONTENT_PADDING diff --git a/internal_packages/composer/stylesheets/composer.less b/internal_packages/composer/stylesheets/composer.less index 94bf45481..b7e819935 100644 --- a/internal_packages/composer/stylesheets/composer.less +++ b/internal_packages/composer/stylesheets/composer.less @@ -322,6 +322,19 @@ body.is-blurred .composer-inner-wrap .tokenizing-field .token { border-radius: @border-radius-base; color: @text-color; + transition-duration: .15s; + // transition-delay: .1s; + transition-property: opacity, margin; + opacity: 0; + visibility: hidden; + margin-top: 3px; + + &.toolbar-visible { + opacity: 1; + visibility: visible; + margin-top: 0px; + } + .toolbar-pointer { position: absolute; width: 22.5px; diff --git a/internal_packages/thread-list/lib/thread-list.cjsx b/internal_packages/thread-list/lib/thread-list.cjsx index 499a2ad8f..97d6c5bf6 100644 --- a/internal_packages/thread-list/lib/thread-list.cjsx +++ b/internal_packages/thread-list/lib/thread-list.cjsx @@ -43,7 +43,7 @@ ThreadList = React.createClass selectedId={@state.selectedId} onSelect={ (item) -> Actions.selectThreadId(item.id) } /> - + _computeColumns: -> labelComponents = (thread) => for label in @state.threadLabelComponents