mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-07-31 20:40:19 +02:00
add code highlighting example, still slow
This commit is contained in:
147
examples/code-highlighting/index.js
Normal file
147
examples/code-highlighting/index.js
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
import Editor, { Mark, Raw, Selection } from '../..'
|
||||
import Prism from 'prismjs'
|
||||
import React from 'react'
|
||||
import keycode from 'keycode'
|
||||
import state from './state.json'
|
||||
|
||||
/**
|
||||
* Node and mark renderers.
|
||||
*/
|
||||
|
||||
const NODES = {
|
||||
code: props => <pre><code>{props.children}</code></pre>,
|
||||
paragraph: props => <p>{props.children}</p>
|
||||
}
|
||||
|
||||
const MARKS = {
|
||||
'highlight-comment': {
|
||||
opacity: '0.33'
|
||||
},
|
||||
'highlight-keyword': {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'highlight-punctuation': {
|
||||
opacity: '0.75'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example.
|
||||
*
|
||||
* @type {Component} CodeHighlighting
|
||||
*/
|
||||
|
||||
class CodeHighlighting extends React.Component {
|
||||
|
||||
state = {
|
||||
state: Raw.deserialize(state)
|
||||
};
|
||||
|
||||
onKeyDown(e, state, editor) {
|
||||
const key = keycode(e.which)
|
||||
if (key != 'enter') return
|
||||
const { startBlock } = state
|
||||
if (startBlock.type != 'code') return
|
||||
|
||||
let transform = state.transform()
|
||||
if (state.isExpanded) transform = transform.delete()
|
||||
transform = transform.insertText('\n')
|
||||
|
||||
return transform.apply()
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="editor">
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={(...args) => this.renderNode(...args)}
|
||||
renderMark={(...args) => this.renderMark(...args)}
|
||||
renderDecorations={(...args) => this.renderDecorations(...args)}
|
||||
onKeyDown={(...args) => this.onKeyDown(...args)}
|
||||
onChange={(state) => {
|
||||
console.groupCollapsed('Change!')
|
||||
console.log('Document:', state.document.toJS())
|
||||
console.log('Selection:', state.selection.toJS())
|
||||
console.log('Content:', Raw.serialize(state))
|
||||
console.groupEnd()
|
||||
this.setState({ state })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
return NODES[node.type]
|
||||
}
|
||||
|
||||
renderMark(mark) {
|
||||
return MARKS[mark.type] || {}
|
||||
}
|
||||
|
||||
renderDecorations(text, state, editor) {
|
||||
let characters = text.characters
|
||||
const { document } = state
|
||||
const block = document.getClosestBlock(text)
|
||||
if (block.type != 'code') return characters
|
||||
|
||||
const string = text.text
|
||||
console.log('render decorations:', string)
|
||||
const grammar = Prism.languages.javascript
|
||||
const tokens = Prism.tokenize(string, grammar)
|
||||
let offset = 0
|
||||
|
||||
for (const token of tokens) {
|
||||
if (typeof token == 'string') {
|
||||
offset += token.length
|
||||
continue
|
||||
}
|
||||
|
||||
const length = offset + token.content.length
|
||||
const type = `highlight-${token.type}`
|
||||
|
||||
for (let i = offset; i < length; i++) {
|
||||
let char = characters.get(i)
|
||||
let { marks } = char
|
||||
marks = marks.add(Mark.create({ type }))
|
||||
char = char.merge({ marks })
|
||||
characters = characters.set(i, char)
|
||||
}
|
||||
|
||||
offset = length
|
||||
}
|
||||
|
||||
return characters
|
||||
}
|
||||
|
||||
// renderDecorations(text) {
|
||||
// const { state } = this.state
|
||||
// const { document } = state
|
||||
// const block = document.getClosestBlock(text)
|
||||
// if (block.type != 'code') return
|
||||
|
||||
// const string = text.text
|
||||
// if (cache[string]) return cache[string]
|
||||
|
||||
// const grammar = Prism.languages.javascript
|
||||
// const tokens = Prism.tokenize(string, grammar)
|
||||
// const ranges = tokens.map((token) => {
|
||||
// return typeof token == 'string'
|
||||
// ? { text: token }
|
||||
// : {
|
||||
// text: token.content,
|
||||
// marks: [{ type: token.type }]
|
||||
// }
|
||||
// })
|
||||
|
||||
// return cached[string] = ranges
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default CodeHighlighting
|
46
examples/code-highlighting/state.json
Normal file
46
examples/code-highlighting/state.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "There are certain behaviors that require rendering dynamic marks on string of text, like rendering code highlighting. For example:"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "code",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "// A simple FizzBuzz implementation.\nfor (var i = 1; i <= 100; i++) {\n if (i % 15 == 0) {\n console.log('Fizz Buzz');\n } else if (i % 5 == 0) {\n console.log('Buzz');\n } else if (i % 3 == 0) {\n console.log('Fizz');\n } else {\n console.log(i);\n }\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "Try it out for yourself!"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -8,6 +8,7 @@ import { Router, Route, Link, IndexRedirect, hashHistory } from 'react-router'
|
||||
*/
|
||||
|
||||
import AutoMarkdown from './auto-markdown'
|
||||
import CodeHighlighting from './code-highlighting'
|
||||
import HoveringMenu from './hovering-menu'
|
||||
import Images from './images'
|
||||
import Links from './links'
|
||||
@@ -32,7 +33,7 @@ class App extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="app">
|
||||
<div className="app">
|
||||
{this.renderTabBar()}
|
||||
{this.renderExample()}
|
||||
</div>
|
||||
@@ -55,6 +56,7 @@ class App extends React.Component {
|
||||
{this.renderTab('Links', 'links')}
|
||||
{this.renderTab('Images', 'images')}
|
||||
{this.renderTab('Tables', 'tables')}
|
||||
{this.renderTab('Code Highlighting', 'code-highlighting')}
|
||||
{this.renderTab('Paste HTML', 'paste-html')}
|
||||
</div>
|
||||
)
|
||||
@@ -100,6 +102,7 @@ const router = (
|
||||
<Route path="/" component={App}>
|
||||
<IndexRedirect to="rich-text" />
|
||||
<Route path="auto-markdown" component={AutoMarkdown} />
|
||||
<Route path="code-highlighting" component={CodeHighlighting} />
|
||||
<Route path="hovering-menu" component={HoveringMenu} />
|
||||
<Route path="images" component={Images} />
|
||||
<Route path="links" component={Links} />
|
||||
|
@@ -49,13 +49,11 @@ class Content extends React.Component {
|
||||
*/
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
// if (props.state.isNative) return false
|
||||
return true
|
||||
// return (
|
||||
// props.state != this.props.state ||
|
||||
// props.state.selection != this.props.state.selection ||
|
||||
// props.state.document != this.props.state.document
|
||||
// )
|
||||
if (props.state.isNative) return false
|
||||
return (
|
||||
props.state.selection != this.props.state.selection ||
|
||||
props.state.document != this.props.state.document
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,22 +63,26 @@ class Content extends React.Component {
|
||||
* @param {Object} props
|
||||
*/
|
||||
|
||||
componentWillMount(props) {
|
||||
componentWillMount() {
|
||||
console.log('is rendering')
|
||||
this.tmp.isRendering = true
|
||||
}
|
||||
|
||||
componentWillUpdate(props) {
|
||||
componentWillUpdate(props, state) {
|
||||
console.log('is rendering')
|
||||
this.tmp.isRendering = true
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
console.log('not rendering')
|
||||
this.tmp.isRendering = false
|
||||
})
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
componentDidUpdate(props, state) {
|
||||
setTimeout(() => {
|
||||
console.log('not rendering')
|
||||
this.tmp.isRendering = false
|
||||
})
|
||||
}
|
||||
@@ -103,11 +105,13 @@ class Content extends React.Component {
|
||||
|
||||
onBlur(e) {
|
||||
if (this.tmp.isCopying) return
|
||||
|
||||
let { state } = this.props
|
||||
let { document, selection } = state
|
||||
selection = selection.merge({ isFocused: false })
|
||||
state = state.merge({ selection })
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.blur()
|
||||
.apply({ isNative: true })
|
||||
|
||||
this.onChange(state)
|
||||
}
|
||||
|
||||
@@ -306,16 +310,17 @@ class Content extends React.Component {
|
||||
const anchor = OffsetKey.findPoint(anchorNode, anchorOffset)
|
||||
const focus = OffsetKey.findPoint(focusNode, focusOffset)
|
||||
|
||||
selection = selection.merge({
|
||||
anchorKey: anchor.key,
|
||||
anchorOffset: anchor.offset,
|
||||
focusKey: focus.key,
|
||||
focusOffset: focus.offset,
|
||||
isFocused: true
|
||||
})
|
||||
state = state
|
||||
.transform()
|
||||
.moveTo({
|
||||
anchorKey: anchor.key,
|
||||
anchorOffset: anchor.offset,
|
||||
focusKey: focus.key,
|
||||
focusOffset: focus.offset
|
||||
})
|
||||
.focus()
|
||||
.apply({ isNative: true })
|
||||
|
||||
selection = selection.normalize(document)
|
||||
state = state.merge({ selection })
|
||||
this.onChange(state)
|
||||
}
|
||||
|
||||
@@ -326,6 +331,7 @@ class Content extends React.Component {
|
||||
*/
|
||||
|
||||
render() {
|
||||
console.log('render contents')
|
||||
const { state } = this.props
|
||||
const { document } = state
|
||||
const children = document.nodes
|
||||
@@ -432,7 +438,7 @@ class Content extends React.Component {
|
||||
*/
|
||||
|
||||
renderText(node) {
|
||||
const { editor, renderMark, renderNode, state } = this.props
|
||||
const { editor, renderMark, state } = this.props
|
||||
return (
|
||||
<Text
|
||||
key={node.key}
|
||||
|
@@ -10,17 +10,21 @@ import corePlugin from '../plugins/core'
|
||||
|
||||
class Editor extends React.Component {
|
||||
|
||||
/**
|
||||
* Properties.
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
plugins: React.PropTypes.array,
|
||||
renderDecorations: React.PropTypes.func,
|
||||
renderMark: React.PropTypes.func,
|
||||
renderNode: React.PropTypes.func,
|
||||
state: React.PropTypes.object,
|
||||
state: React.PropTypes.object.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
plugins: [],
|
||||
state: new State()
|
||||
plugins: []
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,6 +37,7 @@ class Editor extends React.Component {
|
||||
super(props)
|
||||
this.state = {}
|
||||
this.state.plugins = this.resolvePlugins(props)
|
||||
this.state.state = this.resolveState(props.state)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,8 +47,8 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
const plugins = this.resolvePlugins(props)
|
||||
this.setState({ plugins })
|
||||
this.setState({ plugins: this.resolvePlugins(props) })
|
||||
this.setState({ state: this.resolveState(props.state) })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +58,7 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
getState() {
|
||||
return this.props.state
|
||||
return this.state.state
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +68,7 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
onChange(state) {
|
||||
if (state == this.props.state) return
|
||||
if (state == this.state.state) return
|
||||
|
||||
for (const plugin of this.state.plugins) {
|
||||
if (!plugin.onChange) continue
|
||||
@@ -86,9 +91,9 @@ class Editor extends React.Component {
|
||||
onEvent(name, ...args) {
|
||||
for (const plugin of this.state.plugins) {
|
||||
if (!plugin[name]) continue
|
||||
const newState = plugin[name](...args, this.props.state, this)
|
||||
const newState = plugin[name](...args, this.state.state, this)
|
||||
if (!newState) continue
|
||||
this.props.onChange(newState)
|
||||
this.onChange(newState)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -96,14 +101,14 @@ class Editor extends React.Component {
|
||||
/**
|
||||
* Render the editor.
|
||||
*
|
||||
* @return {Component} component
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Content
|
||||
editor={this}
|
||||
state={this.props.state}
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
@@ -118,13 +123,13 @@ class Editor extends React.Component {
|
||||
* Render a `node`, cascading through the plugins.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Component} component
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
renderNode(node) {
|
||||
for (const plugin of this.state.plugins) {
|
||||
if (!plugin.renderNode) continue
|
||||
const component = plugin.renderNode(node, this.props.state, this)
|
||||
const component = plugin.renderNode(node, this.state.state, this)
|
||||
if (component) return component
|
||||
throw new Error(`No renderer found for node with type "${node.type}".`)
|
||||
}
|
||||
@@ -140,7 +145,7 @@ class Editor extends React.Component {
|
||||
renderMark(mark) {
|
||||
for (const plugin of this.state.plugins) {
|
||||
if (!plugin.renderMark) continue
|
||||
const style = plugin.renderMark(mark, this.props.state, this)
|
||||
const style = plugin.renderMark(mark, this.state.state, this)
|
||||
if (style) return style
|
||||
throw new Error(`No renderer found for mark with type "${mark.type}".`)
|
||||
}
|
||||
@@ -169,6 +174,32 @@ class Editor extends React.Component {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the editor's current state from `props` when they change.
|
||||
*
|
||||
* This is where we handle decorating the text nodes with the decorator
|
||||
* functions, so that they are always accounted for when rendering.
|
||||
*
|
||||
* @param {State} state
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
resolveState(state) {
|
||||
const { plugins } = this.state
|
||||
let { document } = state
|
||||
|
||||
document = document.decorateTexts((text) => {
|
||||
for (const plugin of plugins) {
|
||||
if (!plugin.renderDecorations) continue
|
||||
const characters = plugin.renderDecorations(text, state, this)
|
||||
if (characters) return characters
|
||||
}
|
||||
})
|
||||
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -9,6 +9,10 @@ import ReactDOM from 'react-dom'
|
||||
|
||||
class Leaf extends React.Component {
|
||||
|
||||
/**
|
||||
* Properties.
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
marks: React.PropTypes.object.isRequired,
|
||||
node: React.PropTypes.object.isRequired,
|
||||
@@ -19,6 +23,23 @@ class Leaf extends React.Component {
|
||||
text: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Should component update?
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} state
|
||||
* @return {Boolean} shouldUpdate
|
||||
*/
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return (
|
||||
props.start != this.props.start ||
|
||||
props.end != this.props.end ||
|
||||
props.text != this.props.text ||
|
||||
props.marks != this.props.marks
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateSelection()
|
||||
}
|
||||
|
@@ -11,6 +11,10 @@ import { List } from 'immutable'
|
||||
|
||||
class Text extends React.Component {
|
||||
|
||||
/**
|
||||
* Properties.
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
editor: React.PropTypes.object.isRequired,
|
||||
node: React.PropTypes.object.isRequired,
|
||||
@@ -18,8 +22,29 @@ class Text extends React.Component {
|
||||
state: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Should the component update?
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} state
|
||||
* @return {Boolean} shouldUpdate
|
||||
*/
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return (
|
||||
props.node.decorations != this.props.node.decorations ||
|
||||
props.node.characters != this.props.node.characters
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { node } = this.props
|
||||
console.log('render text:', this.props.node.key)
|
||||
return (
|
||||
<span>
|
||||
{this.renderLeaves()}
|
||||
@@ -27,10 +52,17 @@ class Text extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the leaf nodes.
|
||||
*
|
||||
* @return {Array} leaves
|
||||
*/
|
||||
|
||||
renderLeaves() {
|
||||
const { node } = this.props
|
||||
const { characters } = node
|
||||
const ranges = groupByMarks(characters)
|
||||
const { characters, decorations } = node
|
||||
const ranges = groupByMarks(decorations || characters)
|
||||
|
||||
return ranges.map((range, i, ranges) => {
|
||||
const previous = ranges.slice(0, i)
|
||||
const offset = previous.size
|
||||
@@ -40,6 +72,14 @@ class Text extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single leaf node given a `range` and `offset`.
|
||||
*
|
||||
* @param {Object} range
|
||||
* @param {Number} offset
|
||||
* @return {Element} leaf
|
||||
*/
|
||||
|
||||
renderLeaf(range, offset) {
|
||||
const { node, renderMark, state } = this.props
|
||||
const text = range.text
|
||||
|
@@ -19,20 +19,6 @@ class Void extends React.Component {
|
||||
state: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
// onClick(e) {
|
||||
// e.preventDefault()
|
||||
// let { editor, node, state } = this.props
|
||||
// let text = node.getTextNodes().first()
|
||||
|
||||
// state = state
|
||||
// .transform()
|
||||
// .moveToStartOf(text)
|
||||
// .focus()
|
||||
// .apply()
|
||||
|
||||
// editor.onChange(state)
|
||||
// }
|
||||
|
||||
render() {
|
||||
const { children, node } = this.props
|
||||
const Tag = node.kind == 'block' ? 'div' : 'span'
|
||||
|
@@ -58,6 +58,21 @@ const Node = {
|
||||
return this.merge({ nodes })
|
||||
},
|
||||
|
||||
/**
|
||||
* Decorate all of the text nodes with a `decorator` function.
|
||||
*
|
||||
* @param {Function} decorator
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
decorateTexts(decorator) {
|
||||
return this.mapDescendants((child) => {
|
||||
return child.kind == 'text'
|
||||
? child.decorateCharacters(decorator)
|
||||
: child
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Recursively find all ancestor nodes by `iterator`.
|
||||
*
|
||||
|
@@ -285,6 +285,17 @@ class Selection extends Record(DEFAULTS) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the selection to a specific anchor and focus point.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {Selection} selection
|
||||
*/
|
||||
|
||||
moveTo(properties) {
|
||||
return this.merge(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus point to the anchor point.
|
||||
*
|
||||
|
@@ -22,8 +22,7 @@ const DEFAULTS = {
|
||||
document: new Document(),
|
||||
selection: new Selection(),
|
||||
history: new History(),
|
||||
isNative: true,
|
||||
copiedFragment: null
|
||||
isNative: false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -10,7 +10,9 @@ import { List, Record } from 'immutable'
|
||||
|
||||
const DEFAULTS = {
|
||||
characters: new List(),
|
||||
key: null
|
||||
decorations: null,
|
||||
key: null,
|
||||
cache: null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,8 @@ class Text extends Record(DEFAULTS) {
|
||||
if (properties instanceof Text) return properties
|
||||
properties.key = uid(4)
|
||||
properties.characters = Character.createList(properties.characters)
|
||||
properties.decorations = null
|
||||
properties.cache = null
|
||||
return new Text(properties)
|
||||
}
|
||||
|
||||
@@ -65,6 +69,26 @@ class Text extends Record(DEFAULTS) {
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate the text node's characters with a `decorator` function.
|
||||
*
|
||||
* @param {Function} decorator
|
||||
* @return {Text} text
|
||||
*/
|
||||
|
||||
decorateCharacters(decorator) {
|
||||
let { characters, cache } = this
|
||||
if (characters == cache) return this
|
||||
|
||||
const decorations = decorator(this)
|
||||
if (decorations == characters) return this
|
||||
|
||||
return this.merge({
|
||||
cache: characters,
|
||||
decorations,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove characters from the text node from `start` to `end`.
|
||||
*
|
||||
|
@@ -57,6 +57,7 @@ const SELECTION_TRANSFORMS = [
|
||||
'focus',
|
||||
'moveBackward',
|
||||
'moveForward',
|
||||
'moveTo',
|
||||
'moveToAnchor',
|
||||
'moveToEnd',
|
||||
'moveToEndOf',
|
||||
|
@@ -552,7 +552,7 @@ const Transforms = {
|
||||
* Remove an existing `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Mark or String} mark
|
||||
* @param {Mark or String} mark (optional)
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
@@ -575,7 +575,9 @@ const Transforms = {
|
||||
let characters = text.characters.map((char, i) => {
|
||||
if (!isInRange(i, text, range)) return char
|
||||
let { marks } = char
|
||||
marks = marks.remove(mark)
|
||||
marks = mark
|
||||
? marks.remove(mark)
|
||||
: marks.clear()
|
||||
return char.merge({ marks })
|
||||
})
|
||||
|
||||
|
@@ -13,8 +13,13 @@ export default {
|
||||
/**
|
||||
* The core `onBeforeInput` handler.
|
||||
*
|
||||
* If the current selection is collapsed, we can insert the text natively and
|
||||
* avoid a re-render, improving performance.
|
||||
* If the current selection is expanded, we have to re-render.
|
||||
*
|
||||
* If the next state resolves a new list of decorations for any of its text
|
||||
* nodes, we have to re-render.
|
||||
*
|
||||
* Otherwise, we can allow the default, native text insertion, avoiding a
|
||||
* re-render for improved performance.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
@@ -23,14 +28,20 @@ export default {
|
||||
*/
|
||||
|
||||
onBeforeInput(e, state, editor) {
|
||||
const isNative = state.isCollapsed
|
||||
const transform = state.transform().insertText(e.data)
|
||||
const synthetic = transform.apply()
|
||||
const resolved = editor.resolveState(synthetic)
|
||||
|
||||
if (!isNative) e.preventDefault()
|
||||
const isSynthenic = (
|
||||
state.isExpanded ||
|
||||
!resolved.equals(synthetic)
|
||||
)
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertText(e.data)
|
||||
.apply({ isNative })
|
||||
if (isSynthenic) e.preventDefault()
|
||||
|
||||
return isSynthenic
|
||||
? synthetic
|
||||
: transform.apply({ isNative: true })
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -126,9 +137,9 @@ export default {
|
||||
|
||||
paste.text
|
||||
.split('\n')
|
||||
.forEach((block, i) => {
|
||||
.forEach((line, i) => {
|
||||
if (i > 0) transform = transform.splitBlock()
|
||||
transform = transform.insertText(block)
|
||||
transform = transform.insertText(line)
|
||||
})
|
||||
|
||||
return transform.apply()
|
||||
|
@@ -7,10 +7,13 @@
|
||||
"immutable": "^3.8.1",
|
||||
"keycode": "^2.1.2",
|
||||
"lodash": "^4.13.1",
|
||||
"react": "^15.1.0",
|
||||
"ua-parser-js": "^0.7.10",
|
||||
"uid": "0.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0",
|
||||
"react-dom": "^0.14.0 || ^15.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.10.1",
|
||||
"babel-core": "^6.9.1",
|
||||
@@ -25,6 +28,8 @@
|
||||
"exorcist": "^0.4.0",
|
||||
"mocha": "^2.5.3",
|
||||
"mocha-phantomjs": "^4.0.2",
|
||||
"prismjs": "^1.5.1",
|
||||
"react": "^15.2.0",
|
||||
"react-dom": "^15.1.0",
|
||||
"react-portal": "^2.2.0",
|
||||
"react-router": "^2.5.1",
|
||||
|
Reference in New Issue
Block a user