2016-06-15 19:46:53 -07:00
|
|
|
|
2016-06-17 00:09:54 -07:00
|
|
|
import OffsetKey from '../utils/offset-key'
|
2016-06-15 19:46:53 -07:00
|
|
|
import React from 'react'
|
|
|
|
import ReactDOM from 'react-dom'
|
|
|
|
|
|
|
|
/**
|
2016-06-16 12:12:50 -07:00
|
|
|
* Leaf.
|
2016-06-15 19:46:53 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-16 12:12:50 -07:00
|
|
|
class Leaf extends React.Component {
|
2016-06-15 19:46:53 -07:00
|
|
|
|
2016-07-06 14:05:35 -07:00
|
|
|
/**
|
|
|
|
* Properties.
|
|
|
|
*/
|
|
|
|
|
2016-06-15 19:46:53 -07:00
|
|
|
static propTypes = {
|
2016-07-06 20:19:19 -07:00
|
|
|
end: React.PropTypes.number.isRequired,
|
2016-06-18 23:54:08 -07:00
|
|
|
marks: React.PropTypes.object.isRequired,
|
2016-06-15 19:46:53 -07:00
|
|
|
node: React.PropTypes.object.isRequired,
|
|
|
|
renderMark: React.PropTypes.func.isRequired,
|
2016-07-06 20:19:19 -07:00
|
|
|
start: React.PropTypes.number.isRequired,
|
2016-06-15 19:46:53 -07:00
|
|
|
state: React.PropTypes.object.isRequired,
|
2016-06-17 18:20:26 -07:00
|
|
|
text: React.PropTypes.string.isRequired
|
2016-06-15 19:46:53 -07:00
|
|
|
};
|
|
|
|
|
2016-07-06 14:05:35 -07:00
|
|
|
/**
|
|
|
|
* Should component update?
|
|
|
|
*
|
|
|
|
* @param {Object} props
|
|
|
|
* @return {Boolean} shouldUpdate
|
|
|
|
*/
|
|
|
|
|
2016-07-07 19:37:34 -07:00
|
|
|
shouldComponentUpdate(props) {
|
|
|
|
const { start, end, node, state } = props
|
|
|
|
const { selection } = state
|
|
|
|
|
|
|
|
const should = (
|
|
|
|
selection.hasEdgeBetween(node, start, end) ||
|
2016-07-06 14:05:35 -07:00
|
|
|
props.start != this.props.start ||
|
|
|
|
props.end != this.props.end ||
|
|
|
|
props.text != this.props.text ||
|
|
|
|
props.marks != this.props.marks
|
|
|
|
)
|
2016-07-07 19:37:34 -07:00
|
|
|
|
|
|
|
return should
|
2016-07-06 14:05:35 -07:00
|
|
|
}
|
|
|
|
|
2016-06-16 12:12:50 -07:00
|
|
|
componentDidMount() {
|
2016-06-15 19:46:53 -07:00
|
|
|
this.updateSelection()
|
|
|
|
}
|
|
|
|
|
2016-06-16 12:12:50 -07:00
|
|
|
componentDidUpdate() {
|
2016-06-15 19:46:53 -07:00
|
|
|
this.updateSelection()
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSelection() {
|
|
|
|
const { state } = this.props
|
|
|
|
const { selection } = state
|
|
|
|
|
|
|
|
// If the selection is not focused we have nothing to do.
|
2016-06-15 20:13:02 -07:00
|
|
|
if (!selection.isFocused) return
|
2016-06-15 19:46:53 -07:00
|
|
|
|
2016-07-07 19:37:34 -07:00
|
|
|
const { anchorOffset, focusOffset } = selection
|
2016-06-17 18:20:26 -07:00
|
|
|
const { node, start, end } = this.props
|
2016-06-15 19:46:53 -07:00
|
|
|
|
|
|
|
// If neither matches, the selection doesn't start or end here, so exit.
|
2016-07-19 09:37:09 -07:00
|
|
|
const hasAnchor = selection.hasAnchorBetween(node, start, end)
|
|
|
|
const hasFocus = selection.hasFocusBetween(node, start, end)
|
|
|
|
if (!hasAnchor && !hasFocus) return
|
2016-06-15 19:46:53 -07:00
|
|
|
|
|
|
|
// We have a selection to render, so prepare a few things...
|
|
|
|
const native = window.getSelection()
|
|
|
|
const el = ReactDOM.findDOMNode(this).firstChild
|
|
|
|
|
|
|
|
// If both the start and end are here, set the selection all at once.
|
2016-07-19 09:37:09 -07:00
|
|
|
if (hasAnchor && hasFocus) {
|
2016-06-15 19:46:53 -07:00
|
|
|
native.removeAllRanges()
|
2016-07-19 09:37:09 -07:00
|
|
|
const range = window.document.createRange()
|
2016-06-17 18:36:47 -07:00
|
|
|
range.setStart(el, anchorOffset - start)
|
2016-06-15 19:46:53 -07:00
|
|
|
native.addRange(range)
|
2016-06-17 18:36:47 -07:00
|
|
|
native.extend(el, focusOffset - start)
|
2016-06-15 19:46:53 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the selection is forward, we can set things in sequence. In
|
|
|
|
// the first leaf to render, reset the selection and set the new start. And
|
|
|
|
// then in the second leaf to render, extend to the new end.
|
|
|
|
if (selection.isForward) {
|
2016-07-19 09:37:09 -07:00
|
|
|
if (hasAnchor) {
|
2016-06-15 19:46:53 -07:00
|
|
|
native.removeAllRanges()
|
2016-07-19 09:37:09 -07:00
|
|
|
const range = window.document.createRange()
|
2016-06-17 18:36:47 -07:00
|
|
|
range.setStart(el, anchorOffset - start)
|
2016-06-15 19:46:53 -07:00
|
|
|
native.addRange(range)
|
2016-07-19 09:37:09 -07:00
|
|
|
} else if (hasFocus) {
|
2016-06-17 18:36:47 -07:00
|
|
|
native.extend(el, focusOffset - start)
|
2016-06-15 19:46:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, if the selection is backward, we need to hack the order a bit.
|
|
|
|
// In the first leaf to render, set a phony start anchor to store the true
|
|
|
|
// end position. And then in the second leaf to render, set the start and
|
|
|
|
// extend the end to the stored value.
|
|
|
|
else {
|
2016-07-19 09:37:09 -07:00
|
|
|
if (hasFocus) {
|
2016-06-15 19:46:53 -07:00
|
|
|
native.removeAllRanges()
|
2016-07-19 09:37:09 -07:00
|
|
|
const range = window.document.createRange()
|
2016-06-17 18:36:47 -07:00
|
|
|
range.setStart(el, focusOffset - start)
|
2016-06-15 19:46:53 -07:00
|
|
|
native.addRange(range)
|
2016-07-19 09:37:09 -07:00
|
|
|
} else if (hasAnchor) {
|
2016-06-15 19:46:53 -07:00
|
|
|
const endNode = native.focusNode
|
|
|
|
const endOffset = native.focusOffset
|
|
|
|
native.removeAllRanges()
|
2016-07-19 09:37:09 -07:00
|
|
|
const range = window.document.createRange()
|
2016-06-17 18:36:47 -07:00
|
|
|
range.setStart(el, anchorOffset - start)
|
2016-06-15 19:46:53 -07:00
|
|
|
native.addRange(range)
|
|
|
|
native.extend(endNode, endOffset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2016-06-18 23:54:08 -07:00
|
|
|
const { node, text, marks, start, end, renderMark } = this.props
|
2016-06-17 00:09:54 -07:00
|
|
|
const offsetKey = OffsetKey.stringify({
|
|
|
|
key: node.key,
|
2016-06-17 18:20:26 -07:00
|
|
|
start,
|
|
|
|
end
|
2016-06-17 00:09:54 -07:00
|
|
|
})
|
2016-06-15 19:46:53 -07:00
|
|
|
|
2016-07-06 20:19:19 -07:00
|
|
|
const style = marks.reduce((memo, mark) => {
|
2016-06-18 23:54:08 -07:00
|
|
|
return {
|
2016-07-06 20:19:19 -07:00
|
|
|
...memo,
|
2016-07-20 15:11:13 -07:00
|
|
|
...renderMark(mark, marks),
|
2016-06-18 23:54:08 -07:00
|
|
|
}
|
|
|
|
}, {})
|
|
|
|
|
2016-06-15 19:46:53 -07:00
|
|
|
return (
|
|
|
|
<span
|
2016-06-17 00:09:54 -07:00
|
|
|
data-offset-key={offsetKey}
|
2016-06-20 17:38:56 -07:00
|
|
|
style={style}
|
2016-06-15 19:46:53 -07:00
|
|
|
>
|
2016-07-06 20:19:19 -07:00
|
|
|
{text || <br />}
|
2016-06-15 19:46:53 -07:00
|
|
|
</span>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Export.
|
|
|
|
*/
|
|
|
|
|
2016-06-16 12:12:50 -07:00
|
|
|
export default Leaf
|