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:
File diff suppressed because one or more lines are too long
@@ -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 })
|
||||
}}
|
||||
/>
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -30,7 +30,7 @@ class Editor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate(props) {
|
||||
componentWillReceiveProps(props) {
|
||||
const plugins = this.resolvePlugins(props)
|
||||
this.setState({ plugins })
|
||||
}
|
||||
|
@@ -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.
|
||||
|
24
lib/utils/find-offset-key.js
Normal file
24
lib/utils/find-offset-key.js
Normal 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
|
||||
}
|
35
lib/utils/find-selection.js
Normal file
35
lib/utils/find-selection.js
Normal 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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user