delta-component
Version:
embeddable react component
251 lines (198 loc) • 6.95 kB
JavaScript
import React from 'react';
// TODO: https://github.yandex-team.ru/lego/islands/pull/2304
import { decl } from 'islands/common.blocks/i-bem/i-bem.react.js';
import { Editor, findDOMNode } from 'slate-react';
import { Value } from 'slate';
import Plain from 'slate-plain-serializer';
import Prism from 'prismjs';
var diffText = require('diff-text');
//import initialValue from './value.json';
function CodeBlock(props) {
const { editor, node } = props
const language = node.data.get('language')
function onChange(event) {
editor.change(c =>
c.setNodeByKey(node.key, { data: { language: event.target.value } })
)
}
return (
<div style={{ position: 'relative' }}>
<pre>
<code {...props.attributes}>{props.children}</code>
</pre>
</div>
);
}
function CodeBlockLine(props) {
return <div {...props.attributes}>{props.children}</div>
}
export default decl({
block: 'editor',
willMount() {
/*
Получение plain-текста от АПИ
*/
this.plainText = `// A simple FizzBuzz implementation.`;
let slateTreeCodeLines = Plain.deserialize(this.plainText).toJSON().document.nodes;
// debugger;
let slateInitialTree = {
'document': {
nodes: [
{
object: 'block',
type: 'code',
data: {
language: this.props.lang
},
nodes: slateTreeCodeLines
}
]
}
};
this.setState({
value: Value.fromJSON(slateInitialTree),
plainText: this.plainText
});
},
updateDiff(plainText) {
let diff = diffText(' ' + this.state.plainText + ' ', ' ' + plainText + ' ');
let diffChain = diff.map(function(diffItem) {
return diffItem[0];
}).join(',');
let changedLines = [];
// BACKSPACE
if (diffChain === '0,-1,0') {
this.lastDiff = {
type: 'backspace',
data: [diff[1][1].length]
};
changedLines.push((diff[0][1].match(/\n/g) || []).length + 1);
for (let i = changedLines[0]; i < (changedLines[0] + (diff[1][1].match(/\n/g) || []).length); i++) {
changedLines.push(i + 1);
}
this.lastDiff.changedLines = changedLines;
return;
}
// INSERT
if (diffChain === '0,1,0') {
this.lastDiff = {
type: 'insert',
data: [diff[1][1]]
};
changedLines.push((diff[0][1].match(/\n/g) || []).length + 1);
for (let i = changedLines[0]; i < (changedLines[0] + (diff[1][1].match(/\n/g) || []).length); i++) {
changedLines.push(i + 1);
}
this.lastDiff.changedLines = changedLines;
return;
}
// REPLACE
if (diffChain === '0,-1,1,0') {
this.lastDiff = {
type: 'replace',
data: [diff[1][1].length, diff[2][1]]
};
changedLines.push((diff[0][1].match(/\n/g) || []).length + 1);
for (let i = changedLines[0]; i < (changedLines[0] + (diff[1][1].match(/\n/g) || []).length + (diff[2][1].match(/\n/g) || []).length); i++) {
changedLines.push(i + 1);
}
this.lastDiff.changedLines = changedLines;
return;
}
// кейс, когда заменяющая строка является подстрокой заменяемой
if (diffChain === '0,-1,0,1,0') {
this.lastDiff = {
type: 'replace',
data: [diff[1][1].length + diff[2][1].length + diff[3][1].length, diff[2][1]]
};
changedLines.push((diff[0][1].match(/\n/g) || []).length + 1);
for (let i = changedLines[0]; i < (changedLines[0] + (diff[1][1].match(/\n/g) || []).length
+ (diff[2][1].match(/\n/g) || []).length
+ (diff[3][1].match(/\n/g) || []).length); i++) {
changedLines.push(i + 1);
}
this.lastDiff.changedLines = changedLines;
return;
}
this.lastDiff = null;
},
onChange(change) {
let value = change.value;
let plainText = Plain.serialize(value);
this.updateDiff(plainText);
this.decoratedLine = 0;
this.setState({ value: value, plainText: plainText });
},
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode(props) {
switch (props.node.type) {
case 'code':
return <CodeBlock {...props} />
case 'line':
return <CodeBlockLine {...props} />
}
},
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark(props) {
const { children, mark, attributes } = props;
return <span {...attributes} className={mark.type + ' token'}>{children}</span>;
},
tokenToContent(token) {
if (typeof token == 'string') {
return token
} else if (typeof token.content == 'string') {
return token.content
} else {
return token.content.map(this.tokenToContent).join('')
}
},
onPaste(e) {
//debugger;
},
/**
* Decorate code blocks with Prism.js highlighting.
*
* @param {Node} node
* @return {Array}
*/
decorateNode(node) {
if (node.type != 'line') return;
const language = 'javascript'
const textNode = node.getTexts().get(0);
const string = node.getText();
const grammar = Prism.languages[language]
const tokens = Prism.tokenize(string, grammar)
const decorations = [];
for (const token of tokens) {
const content = this.tokenToContent(token)
if (typeof token != 'string') {
const range = {
anchorKey: textNode.key,
anchorOffset: string.indexOf(token.content),
focusKey: textNode.key,
focusOffset: string.indexOf(token.content) + token.length,
marks: [{ type: token.type }],
}
decorations.push(range)
}
}
return decorations;
},
content() {
return <Editor
placeholder="Write some code..."
value={this.state.value}
onChange={this.onChange.bind(this)}
/>;
}
});