mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 02:19:52 +02:00
fix text nodes to rerender with changes decorations
This commit is contained in:
@@ -11,7 +11,41 @@ import initialState from './state.json'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const NODES = {
|
const NODES = {
|
||||||
code: props => <pre><code {...props.attributes}>{props.children}</code></pre>
|
code: (props) => {
|
||||||
|
const { attributes, children, editor, node } = props
|
||||||
|
const language = node.data.get('language')
|
||||||
|
|
||||||
|
function onChange(e) {
|
||||||
|
const state = editor.getState()
|
||||||
|
const next = state
|
||||||
|
.transform()
|
||||||
|
.setNodeByKey(node.key, {
|
||||||
|
data: {
|
||||||
|
language: e.target.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.apply()
|
||||||
|
editor.onChange(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<pre>
|
||||||
|
<code {...props.attributes}>{props.children}</code>
|
||||||
|
</pre>
|
||||||
|
<div
|
||||||
|
contentEditable={false}
|
||||||
|
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||||
|
>
|
||||||
|
<select value={language} onChange={onChange} >
|
||||||
|
<option value="css">CSS</option>
|
||||||
|
<option value="js">JavaScript</option>
|
||||||
|
<option value="html">HTML</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,8 +170,9 @@ class CodeHighlighting extends React.Component {
|
|||||||
if (block.type != 'code') return text.characters
|
if (block.type != 'code') return text.characters
|
||||||
|
|
||||||
let characters = text.characters.asMutable()
|
let characters = text.characters.asMutable()
|
||||||
|
const language = block.data.get('language')
|
||||||
const string = text.text
|
const string = text.text
|
||||||
const grammar = Prism.languages.javascript
|
const grammar = Prism.languages[language]
|
||||||
const tokens = Prism.tokenize(string, grammar)
|
const tokens = Prism.tokenize(string, grammar)
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
|
||||||
|
@@ -13,6 +13,9 @@
|
|||||||
{
|
{
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"type": "code",
|
"type": "code",
|
||||||
|
"data": {
|
||||||
|
"language": "js"
|
||||||
|
},
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
|
@@ -4,7 +4,7 @@ import Debug from 'debug'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import TYPES from '../utils/types'
|
import TYPES from '../utils/types'
|
||||||
import Text from './text'
|
import Leaf from './leaf'
|
||||||
import Void from './void'
|
import Void from './void'
|
||||||
import scrollTo from '../utils/scroll-to'
|
import scrollTo from '../utils/scroll-to'
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ class Node extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
block: React.PropTypes.object,
|
||||||
editor: React.PropTypes.object.isRequired,
|
editor: React.PropTypes.object.isRequired,
|
||||||
node: React.PropTypes.object.isRequired,
|
node: React.PropTypes.object.isRequired,
|
||||||
renderDecorations: React.PropTypes.func.isRequired,
|
renderDecorations: React.PropTypes.func.isRequired,
|
||||||
@@ -47,7 +48,7 @@ class Node extends React.Component {
|
|||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
style: {}
|
style: {}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@@ -130,6 +131,17 @@ class Node extends React.Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For text nodes, which can have custom decorations, we need to check to
|
||||||
|
// see if the block has changed, which has caused the decorations to change.
|
||||||
|
if (
|
||||||
|
props.node.kind == 'text' &&
|
||||||
|
props.block != this.props.block
|
||||||
|
) {
|
||||||
|
const nextRanges = props.node.getDecoratedRanges(props.block, props.renderDecorations)
|
||||||
|
const ranges = this.props.node.getDecoratedRanges(this.props.block, this.props.renderDecorations)
|
||||||
|
if (!ranges.equals(nextRanges)) return true
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, don't update.
|
// Otherwise, don't update.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -216,10 +228,12 @@ class Node extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = (child) => {
|
renderNode = (child) => {
|
||||||
const { editor, renderDecorations, renderMark, renderNode, state } = this.props
|
const { editor, node, renderDecorations, renderMark, renderNode, state } = this.props
|
||||||
|
const block = node.kind == 'block' ? node : this.props.block
|
||||||
return (
|
return (
|
||||||
<Node
|
<Node
|
||||||
key={child.key}
|
key={child.key}
|
||||||
|
block={block}
|
||||||
node={child}
|
node={child}
|
||||||
state={state}
|
state={state}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
@@ -281,15 +295,48 @@ class Node extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderText = () => {
|
renderText = () => {
|
||||||
const { node, editor, renderDecorations, renderMark, state } = this.props
|
const { node, block, renderDecorations } = this.props
|
||||||
|
const ranges = node.getDecoratedRanges(block, renderDecorations)
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
|
const leaves = ranges.map((range, i, original) => {
|
||||||
|
const leaf = this.renderLeaf(ranges, range, i, offset)
|
||||||
|
offset += range.text.length
|
||||||
|
return leaf
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<span data-key={node.key}>
|
||||||
key={node.key}
|
{leaves}
|
||||||
editor={editor}
|
</span>
|
||||||
node={node}
|
)
|
||||||
renderDecorations={renderDecorations}
|
}
|
||||||
renderMark={renderMark}
|
|
||||||
|
/**
|
||||||
|
* Render a single leaf node given a `range` and `offset`.
|
||||||
|
*
|
||||||
|
* @param {List} ranges
|
||||||
|
* @param {Range} range
|
||||||
|
* @param {Number} index
|
||||||
|
* @param {Number} offset
|
||||||
|
* @return {Element} leaf
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderLeaf = (ranges, range, index, offset) => {
|
||||||
|
const { node, renderMark, state } = this.props
|
||||||
|
const text = range.text
|
||||||
|
const marks = range.marks
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Leaf
|
||||||
|
key={`${node.key}-${index}`}
|
||||||
|
index={index}
|
||||||
state={state}
|
state={state}
|
||||||
|
node={node}
|
||||||
|
text={text}
|
||||||
|
marks={marks}
|
||||||
|
ranges={ranges}
|
||||||
|
renderMark={renderMark}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -298,6 +345,8 @@ class Node extends React.Component {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
|
*
|
||||||
|
* @type {Component}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default Node
|
export default Node
|
||||||
|
@@ -1,109 +0,0 @@
|
|||||||
|
|
||||||
import Leaf from './leaf'
|
|
||||||
import React from 'react'
|
|
||||||
import { List } from 'immutable'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Text extends React.Component {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Properties.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
editor: React.PropTypes.object.isRequired,
|
|
||||||
node: React.PropTypes.object.isRequired,
|
|
||||||
renderDecorations: React.PropTypes.func.isRequired,
|
|
||||||
renderMark: React.PropTypes.func.isRequired,
|
|
||||||
state: React.PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the component update?
|
|
||||||
*
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {Object} state
|
|
||||||
* @return {Boolean} shouldUpdate
|
|
||||||
*/
|
|
||||||
|
|
||||||
shouldComponentUpdate(props, state) {
|
|
||||||
return (
|
|
||||||
props.node != this.props.node ||
|
|
||||||
(props.state.isFocused && props.state.selection.hasEdgeIn(props.node))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render.
|
|
||||||
*
|
|
||||||
* @return {Element} element
|
|
||||||
*/
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { node } = this.props
|
|
||||||
return (
|
|
||||||
<span data-key={node.key}>
|
|
||||||
{this.renderLeaves()}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the leaf nodes.
|
|
||||||
*
|
|
||||||
* @return {Array} leaves
|
|
||||||
*/
|
|
||||||
|
|
||||||
renderLeaves() {
|
|
||||||
const { node, state, renderDecorations } = this.props
|
|
||||||
const block = state.document.getClosestBlock(node)
|
|
||||||
const ranges = node.getDecoratedRanges(block, renderDecorations)
|
|
||||||
|
|
||||||
return ranges.map((range, i, original) => {
|
|
||||||
const previous = original.slice(0, i)
|
|
||||||
const offset = previous.size
|
|
||||||
? previous.map(r => r.text).join('').length
|
|
||||||
: 0
|
|
||||||
return this.renderLeaf(ranges, range, i, offset)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a single leaf node given a `range` and `offset`.
|
|
||||||
*
|
|
||||||
* @param {List} ranges
|
|
||||||
* @param {Range} range
|
|
||||||
* @param {Number} index
|
|
||||||
* @param {Number} offset
|
|
||||||
* @return {Element} leaf
|
|
||||||
*/
|
|
||||||
|
|
||||||
renderLeaf(ranges, range, index, offset) {
|
|
||||||
const { node, renderMark, state } = this.props
|
|
||||||
const text = range.text
|
|
||||||
const marks = range.marks
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Leaf
|
|
||||||
key={`${node.key}-${index}`}
|
|
||||||
index={index}
|
|
||||||
state={state}
|
|
||||||
node={node}
|
|
||||||
text={text}
|
|
||||||
marks={marks}
|
|
||||||
ranges={ranges}
|
|
||||||
renderMark={renderMark}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default Text
|
|
Reference in New Issue
Block a user