1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-18 13:11:17 +02:00

add support for spellcheck

This commit is contained in:
Ian Storm Taylor
2016-07-22 20:21:50 -07:00
parent 20874faa69
commit 2a58f71c42
4 changed files with 77 additions and 4 deletions

View File

@@ -37,6 +37,7 @@ class Content extends React.Component {
readOnly: React.PropTypes.bool, readOnly: React.PropTypes.bool,
renderMark: React.PropTypes.func.isRequired, renderMark: React.PropTypes.func.isRequired,
renderNode: React.PropTypes.func.isRequired, renderNode: React.PropTypes.func.isRequired,
spellCheck: React.PropTypes.bool,
state: React.PropTypes.object.isRequired, state: React.PropTypes.object.isRequired,
style: React.PropTypes.object style: React.PropTypes.object
}; };
@@ -47,6 +48,7 @@ class Content extends React.Component {
static defaultProps = { static defaultProps = {
readOnly: false, readOnly: false,
spellCheck: true,
style: {} style: {}
}; };
@@ -376,6 +378,50 @@ class Content extends React.Component {
this.props.onDrop(e, drop) this.props.onDrop(e, drop)
} }
/**
* On input, handle spellcheck and other similar edits that don't go trigger
* the `onBeforeInput` and instead update the DOM directly.
*
* @param {Event} e
*/
onInput = (e) => {
let { state } = this.props
const { selection } = state
const native = window.getSelection()
const { anchorNode, anchorOffset, focusOffset } = native
const { textContent } = anchorNode
const offsetKey = OffsetKey.findKey(anchorNode)
const { key, index } = OffsetKey.parse(offsetKey)
const { start, end } = OffsetKey.findBounds(key, index, state)
const range = OffsetKey.findRange(anchorNode, state)
const { text, marks } = range
// If the text is no different, abort.
if (textContent == text) return
// Determine what the selection should be after changing the text.
const delta = textContent.length - text.length
const after = selection.collapseToEnd().moveForward(delta)
// Create an updated state with the text replaced.
state = state
.transform()
.moveTo({
anchorKey: key,
anchorOffset: start,
focusKey: key,
focusOffset: end
})
.delete()
.insertText(textContent, marks)
.moveTo(after)
.apply()
// Change the current state.
this.onChange(state)
}
/** /**
* On key down, prevent the default behavior of certain commands that will * On key down, prevent the default behavior of certain commands that will
* leave the editor in an out-of-sync state, then bubble up. * leave the editor in an out-of-sync state, then bubble up.
@@ -547,6 +593,11 @@ class Content extends React.Component {
...this.props.style, ...this.props.style,
} }
// COMPAT: In Firefox, spellchecking can remove entire wrapping elements
// including inline ones like `<a>`, which is jarring for the user but also
// causes the DOM to get into an irreconilable state.
const spellCheck = IS_FIREFOX ? false : this.props.spellCheck
return ( return (
<div <div
key={`slate-content-${this.forces}`} key={`slate-content-${this.forces}`}
@@ -563,10 +614,12 @@ class Content extends React.Component {
onDragOver={this.onDragOver} onDragOver={this.onDragOver}
onDragStart={this.onDragStart} onDragStart={this.onDragStart}
onDrop={this.onDrop} onDrop={this.onDrop}
onInput={this.onInput}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
onKeyUp={noop} onKeyUp={noop}
onPaste={this.onPaste} onPaste={this.onPaste}
onSelect={this.onSelect} onSelect={this.onSelect}
spellCheck={spellCheck}
style={style} style={style}
> >
{children} {children}

View File

@@ -703,10 +703,11 @@ class State extends new Record(DEFAULTS) {
* Insert a `text` string at the current selection. * Insert a `text` string at the current selection.
* *
* @param {String} text * @param {String} text
* @param {Set} marks (optional)
* @return {State} state * @return {State} state
*/ */
insertText(text) { insertText(text, marks) {
let state = this let state = this
let { cursorMarks, document, selection } = state let { cursorMarks, document, selection } = state
let after = selection let after = selection
@@ -721,7 +722,7 @@ class State extends new Record(DEFAULTS) {
} }
// Insert the text and update the selection. // Insert the text and update the selection.
document = document.insertTextAtRange(selection, text, cursorMarks) document = document.insertTextAtRange(selection, text, marks || cursorMarks)
selection = after selection = after
state = state.merge({ document, selection }) state = state.merge({ document, selection })
return state return state

View File

@@ -66,7 +66,7 @@ function findKey(element) {
} }
/** /**
* Find the selection point from an `element`, `offset`, and list of `ranges`. * Find the selection point from an `element`, `offset`, and `state`.
* *
* @param {Element} element * @param {Element} element
* @param {Offset} offset * @param {Offset} offset
@@ -90,6 +90,23 @@ function findPoint(element, offset, state) {
} }
} }
/**
* Find the range from an `element`.
*
* @param {Element} element
* @param {State} state
* @return {Range} range
*/
function findRange(element, state) {
const offsetKey = findKey(element)
const { key, index } = parse(offsetKey)
const text = state.document.getDescendant(key)
const ranges = text.getDecoratedRanges()
const range = ranges.get(index)
return range
}
/** /**
* Parse an offset key `string`. * Parse an offset key `string`.
* *
@@ -103,7 +120,7 @@ function parse(string) {
const [ original, key, index ] = matches const [ original, key, index ] = matches
return { return {
key, key,
index index: parseInt(index, 10)
} }
} }
@@ -128,6 +145,7 @@ export default {
findBounds, findBounds,
findKey, findKey,
findPoint, findPoint,
findRange,
parse, parse,
stringify stringify
} }

View File

@@ -54,6 +54,7 @@ function clean(html) {
$(el).removeAttr('data-offset-key') $(el).removeAttr('data-offset-key')
}) })
$.root().children().removeAttr('spellcheck')
$.root().children().removeAttr('style') $.root().children().removeAttr('style')
return $.html() return $.html()