Mailspring/internal_packages/composer/lib/floating-toolbar.cjsx
Evan Morikawa e7868df1ad feat(contenteditable): a React compatible contenteditable
Summary:
toolbar popup displays

restore caret protection on contenteditable

BAD - can't use cursor saving and restoring with react :(

_findNode works

saves and restores cursor state

contenteditable fixes to support cursor

comments on cursor

initial undo manager

extract undo manager and move up in stack

make undo manager a mixin

adding selection snapshots in composer

fixes in undo manager

selection saves selection states properly

move UndoManager and fix draft

selection state can now select backwards

selection works backwards and click not overridden

change bold class to allow for bolding and unbolding

styling of hover component

can set links in composer

bold and italic clicking works. text seleciton works

show link modal on hover

selection fixes

Test Plan: TODO

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://review.inboxapp.com/D1249
2015-03-02 15:33:58 -08:00

157 lines
4.9 KiB
CoffeeScript

_ = require 'underscore-plus'
React = require 'react'
{CompositeDisposable} = require 'event-kit'
module.exports =
FloatingToolbar = React.createClass
getInitialState: ->
mode: "buttons"
urlInputValue: @_initialUrl() ? ""
componentDidMount: ->
@isHovering = false
@subscriptions = new CompositeDisposable()
@_saveUrl = _.debounce @__saveUrl, 10
componentWillReceiveProps: (nextProps) ->
@setState
mode: nextProps.initialMode
urlInputValue: @_initialUrl(nextProps)
componentWillUnmount: ->
@subscriptions?.dispose()
@isHovering = false
componentDidUpdate: ->
if @state.mode is "edit-link" and not @props.linkToModify
# Note, it's important that we're focused on the urlInput because
# the parent of this component needs to know to not hide us on their
# onBlur method.
@refs.urlInput.getDOMNode().focus() if @isMounted()
render: ->
<div ref="floatingToolbar"
className="floating-toolbar toolbar" style={@_toolbarStyles()}>
<div className="toolbar-pointer" style={@_toolbarPointerStyles()}></div>
{@_toolbarType()}
</div>
_toolbarType: ->
if @state.mode is "buttons" then @_renderButtons()
else if @state.mode is "edit-link" then @_renderLink()
else return <div></div>
_renderButtons: ->
<div className="toolbar-buttons">
<button className="btn btn-bold btn-icon"
onClick={@_execCommand}
data-command-name="bold"><strong>B</strong></button>
<button className="btn btn-italic btn-icon"
onClick={@_execCommand}
data-command-name="italic"><em>I</em></button>
<button className="btn btn-link btn-icon"
onClick={@_showLink}
data-command-name="link"><i className="fa fa-link"></i></button>
</div>
_renderLink: ->
removeBtn = ""
if @_initialUrl()
removeBtn = <button className="btn btn-icon"
onMouseDown={@_removeUrl}><i className="fa fa-times"></i></button>
<div className="toolbar-new-link"
onMouseEnter={@_onMouseEnter}
onMouseLeave={@_onMouseLeave}>
<i className="fa fa-link preview-btn-icon"></i>
<input type="text"
ref="urlInput"
value={@state.urlInputValue}
onBlur={@_saveUrl}
onKeyPress={@_saveUrlOnEnter}
onChange={@_onInputChange}
className="floating-toolbar-input"
placeholder="Paste or type a link" />
<button className="btn btn-icon"
onKeyPress={@_saveUrlOnEnter}
onMouseDown={@_saveUrl}><i className="fa fa-check"></i></button>
{removeBtn}
</div>
_onMouseEnter: ->
@isHovering = true
@props.onMouseEnter?()
_onMouseLeave: ->
@isHovering = false
if @props.linkToModify and document.activeElement isnt @refs.urlInput.getDOMNode()
@props.onMouseLeave?()
_initialUrl: (props=@props) ->
props.linkToModify?.getAttribute?('href')
_onInputChange: (event) ->
@setState urlInputValue: event.target.value
_saveUrlOnEnter: (event) ->
if event.key is "Enter" and @state.urlInputValue.trim().length > 0
@_saveUrl()
# We signify the removal of a url with an empty string. This protects us
# from the case where people delete the url text and hit save. In that
# case we also want to remove the link.
_removeUrl: ->
@setState urlInputValue: ""
@props.onSaveUrl "", @props.linkToModify
__saveUrl: ->
@props.onSaveUrl @state.urlInputValue, @props.linkToModify
_execCommand: (event) ->
cmd = event.currentTarget.getAttribute 'data-command-name'
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
left = Math.min(Math.max(@props.left - @_halfWidth(), CONTENT_PADDING), max)
return left
_toolbarPointerStyles: ->
CONTENT_PADDING = @props.contentPadding ? 15
POINTER_WIDTH = 6 + 2 #2px of border-radius
max = @props.editAreaWidth - CONTENT_PADDING
min = CONTENT_PADDING
absoluteLeft = Math.max(Math.min(@props.left, max), min)
relativeLeft = absoluteLeft - @_toolbarLeft()
left = Math.max(Math.min(relativeLeft, @_halfWidth()*2-POINTER_WIDTH), POINTER_WIDTH)
styles =
left: left
return styles
_halfWidth: ->
# We can't calculate the width of the floating toolbar declaratively
# because it hasn't been rendered yet. As such, we'll keep the width
# fixed to make it much eaier.
TOOLBAR_BUTTONS_WIDTH = 86#px
TOOLBAR_URL_WIDTH = 210#px
if @state.mode is "buttons"
TOOLBAR_BUTTONS_WIDTH / 2
else if @state.mode is "edit-link"
TOOLBAR_URL_WIDTH / 2
else
TOOLBAR_BUTTONS_WIDTH / 2
_showLink: ->
@setState mode: "edit-link"