1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-29 18:09:49 +02:00

got basic selection working

This commit is contained in:
Ian Storm Taylor
2016-06-15 14:47:52 -07:00
parent 567884c9f2
commit 64574c4f64
7 changed files with 640 additions and 321 deletions

File diff suppressed because one or more lines are too long

View File

@@ -125,7 +125,7 @@ class App extends React.Component {
renderMark={renderMark}
state={this.state.state}
onChange={(state) => {
console.log('State:', state)
console.log('Change:', state.toJS())
this.setState({ state })
}}
/>

View File

@@ -1,7 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom'
import TextNode from './text-node'
import TextNodeModel from '../models/text-node'
import findSelection from '../utils/find-selection'
import keycode from 'keycode'
/**
@@ -10,33 +12,92 @@ import keycode from 'keycode'
class Content extends React.Component {
/**
* Props.
*/
static propTypes = {
onChange: React.PropTypes.func,
onKeyDown: React.PropTypes.func,
onSelect: React.PropTypes.func,
renderMark: React.PropTypes.func.isRequired,
renderNode: React.PropTypes.func.isRequired,
state: React.PropTypes.object.isRequired,
};
/**
* On change, bubble up.
*
* @param {State} state
*/
onChange(state) {
this.props.onChange(state)
}
onKeyDown(e) {
const key = keycode(e.which)
/**
* On key down, bubble up.
*
* @param {Event} e
*/
onKeyDown(e) {
// COMPAT: Certain keys should never be handled by the browser's mechanism,
// because using the native contenteditable behavior introduces quirks.
if (key === 'escape' || key === 'return') {
e.preventDefault()
}
const key = keycode(e.which)
if (key === 'escape' || key === 'return') e.preventDefault()
this.props.onKeyDown(e)
}
/**
* On select, update the current state's selection.
*
* @param {Event} e
*/
onSelect(e) {
let { state } = this.props
let { selection } = state
const native = window.getSelection()
// no selection is active, so unset `hasFocus`
if (!native.rangeCount && selection.hasFocus) {
selection = selection.set('hasFocus', false)
state = state.set('selection', selection)
this.onChange(state)
return
}
const el = ReactDOM.findDOMNode(this)
const { anchorNode, anchorOffset, focusNode, focusOffset } = native
const anchorIsText = anchorNode.nodeType == 3
const focusIsText = focusNode.nodeType == 3
// if both text nodes, find their parent's and create the selection
if (anchorIsText && focusIsText) {
const anchor = findSelection(anchorNode, anchorOffset)
const focus = findSelection(focusNode, focusOffset)
selection = selection.set('anchorKey', anchor.key)
selection = selection.set('anchorOffset', anchor.offset)
selection = selection.set('focusKey', focus.key)
selection = selection.set('focusOffset', focus.offset)
selection = selection.set('hasFocus', true)
state = state.set('selection', selection)
this.onChange(state)
return
}
}
/**
* Render the editor content.
*
* @return {Component} component
*/
render() {
const { state } = this.props
const { nodes, selection } = state
const { nodes } = state
const children = nodes
.toArray()
.map(node => this.renderNode(node))
@@ -47,12 +108,20 @@ class Content extends React.Component {
suppressContentEditableWarning
data-type='content'
onKeyDown={(e) => this.onKeyDown(e)}
onSelect={(e) => this.onSelect(e)}
>
{children}
</div>
)
}
/**
* Render a single `node`.
*
* @param {Node} node
* @return {Component} component
*/
renderNode(node) {
const { renderMark, renderNode } = this.props
@@ -87,3 +156,4 @@ class Content extends React.Component {
*/
export default Content

View File

@@ -30,7 +30,7 @@ class Editor extends React.Component {
}
}
componentWillUpdate(props) {
componentWillReceiveProps(props) {
const plugins = this.resolvePlugins(props)
this.setState({ plugins })
}

View File

@@ -20,7 +20,7 @@ class TextNode extends React.Component {
const ranges = characters
.toArray()
.reduce((ranges, char, i) => {
const previous = characters[i - 1]
const previous = i == 0 ? null : characters.get(i - 1)
const { text } = char
const marks = char.marks.toArray().map(mark => mark.type)
@@ -49,16 +49,25 @@ class TextNode extends React.Component {
}, {})
return (
<LeafNode
<span
key={key}
styles={styles}
text={range.text}
/>
data-key={key}
data-type='leaf'
style={styles}
>
{range.text}
</span>
)
})
return (
<span key={node.key} data-type='text'>{leaves}</span>
<span
key={node.key}
data-key={node.key}
data-type='text'
>
{leaves}
</span>
)
}
@@ -109,21 +118,21 @@ class TextNode extends React.Component {
}
/**
* Find matching `marks` at `index`.
*
* @param {Array} marks
* @param {Number} index
* @return {Array} marks
*/
// /**
// * Find matching `marks` at `index`.
// *
// * @param {Array} marks
// * @param {Number} index
// * @return {Array} marks
// */
function findMarks(marks, index) {
return marks
.filter(mark => mark.start < index)
.filter(mark => mark.end + 1 > index)
.map(mark => mark.type)
.sort()
}
// function findMarks(marks, index) {
// return marks
// .filter(mark => mark.start < index)
// .filter(mark => mark.end + 1 > index)
// .map(mark => mark.type)
// .sort()
// }
/**
* Export.

View File

@@ -0,0 +1,24 @@
/**
* Find the nearest parent of a `node` and return their offset key.
*
* @param {Node} node
* @return {String} key
*/
export default function findOffsetKey(node) {
let match = node
while (match && match != document.documentElement) {
if (
match instanceof Element &&
match.hasAttribute('data-key')
) {
return match.getAttribute('data-key')
}
match = match.parentNode
}
return null
}

View File

@@ -0,0 +1,35 @@
import findOffsetKey from './find-offset-key'
/**
* Offset key splitter.
*/
const SPLITTER = /^(\d+)(?:\.(\d+)-(\d+))?$/
/**
* Find the selection anchor properties from a `node`.
*
* @param {Node} node
* @param {Number} nodeOffset
* @return {Object} anchor
* @property {String} anchorKey
* @property {Number} anchorOffset
*/
export default function findSelection(node, nodeOffset) {
const offsetKey = findOffsetKey(node)
if (!offsetKey) return null
const matches = SPLITTER.exec(offsetKey)
if (!matches) throw new Error(`Unknown offset key "${offsetKey}".`)
let [ all, key, offsetStart, offsetEnd ] = matches
offsetStart = parseInt(offsetStart, 10)
offsetEnd = parseInt(offsetEnd, 10)
return {
key: key,
offset: offsetStart + nodeOffset
}
}