1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-03-06 13:59:47 +01:00
2018-03-21 15:34:20 -07:00

279 lines
5.6 KiB
JavaScript

import Html from 'slate-html-serializer'
import { Editor, getEventTransfer } from 'slate-react'
import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
/**
* Tags to blocks.
*
* @type {Object}
*/
const BLOCK_TAGS = {
p: 'paragraph',
li: 'list-item',
ul: 'bulleted-list',
ol: 'numbered-list',
blockquote: 'quote',
pre: 'code',
h1: 'heading-one',
h2: 'heading-two',
h3: 'heading-three',
h4: 'heading-four',
h5: 'heading-five',
h6: 'heading-six',
}
/**
* Tags to marks.
*
* @type {Object}
*/
const MARK_TAGS = {
strong: 'bold',
em: 'italic',
u: 'underline',
s: 'strikethrough',
code: 'code',
}
/**
* Serializer rules.
*
* @type {Array}
*/
const RULES = [
{
deserialize(el, next) {
const block = BLOCK_TAGS[el.tagName.toLowerCase()]
if (block) {
return {
object: 'block',
type: block,
nodes: next(el.childNodes),
}
}
},
},
{
deserialize(el, next) {
const mark = MARK_TAGS[el.tagName.toLowerCase()]
if (mark) {
return {
object: 'mark',
type: mark,
nodes: next(el.childNodes),
}
}
},
},
{
// Special case for code blocks, which need to grab the nested childNodes.
deserialize(el, next) {
if (el.tagName.toLowerCase() == 'pre') {
const code = el.childNodes[0]
const childNodes =
code && code.tagName.toLowerCase() == 'code'
? code.childNodes
: el.childNodes
return {
object: 'block',
type: 'code',
nodes: next(childNodes),
}
}
},
},
{
// Special case for images, to grab their src.
deserialize(el, next) {
if (el.tagName.toLowerCase() == 'img') {
return {
object: 'block',
type: 'image',
isVoid: true,
nodes: next(el.childNodes),
data: {
src: el.getAttribute('src'),
},
}
}
},
},
{
// Special case for links, to grab their href.
deserialize(el, next) {
if (el.tagName.toLowerCase() == 'a') {
return {
object: 'inline',
type: 'link',
nodes: next(el.childNodes),
data: {
href: el.getAttribute('href'),
},
}
}
},
},
]
/**
* Create a new HTML serializer with `RULES`.
*
* @type {Html}
*/
const serializer = new Html({ rules: RULES })
/**
* The pasting html example.
*
* @type {Component}
*/
class PasteHtml extends React.Component {
/**
* Deserialize the raw initial value.
*
* @type {Object}
*/
state = {
value: Value.fromJSON(initialValue),
}
/**
* On change, save the new value.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On paste, deserialize the HTML and then insert the fragment.
*
* @param {Event} event
* @param {Change} change
*/
onPaste = (event, change) => {
const transfer = getEventTransfer(event)
if (transfer.type != 'html') return
const { document } = serializer.deserialize(transfer.html)
change.insertFragment(document)
return true
}
/**
* Render.
*
* @return {Component}
*/
render() {
return (
<div className="editor">
<Editor
placeholder="Paste in some HTML..."
value={this.state.value}
onPaste={this.onPaste}
onChange={this.onChange}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node, isSelected } = props
switch (node.type) {
case 'quote':
return <blockquote {...attributes}>{children}</blockquote>
case 'code':
return (
<pre>
<code {...attributes}>{children}</code>
</pre>
)
case 'bulleted-list':
return <ul {...attributes}>{children}</ul>
case 'heading-one':
return <h1 {...attributes}>{children}</h1>
case 'heading-two':
return <h2 {...attributes}>{children}</h2>
case 'heading-three':
return <h3 {...attributes}>{children}</h3>
case 'heading-four':
return <h4 {...attributes}>{children}</h4>
case 'heading-five':
return <h5 {...attributes}>{children}</h5>
case 'heading-six':
return <h6 {...attributes}>{children}</h6>
case 'list-item':
return <li {...attributes}>{children}</li>
case 'numbered-list':
return <ol {...attributes}>{children}</ol>
case 'link': {
const { data } = node
const href = data.get('href')
return (
<a href={href} {...attributes}>
{children}
</a>
)
}
case 'image': {
const src = node.data.get('src')
const className = isSelected ? 'active' : null
const style = { display: 'block' }
return (
<img src={src} className={className} style={style} {...attributes} />
)
}
}
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
case 'code':
return <code>{children}</code>
case 'italic':
return <em>{children}</em>
case 'underlined':
return <u>{children}</u>
}
}
}
/**
* Export.
*/
export default PasteHtml