任务说明:
右侧部分包含可拖动的 HTML 元素(使用 beautiful-react-dnd 开发)。
在左侧部分使用 slate js 和 slate-react 开发了一个文本编辑器,但是当我尝试将 div 部分从右侧部分拖放到编辑器(在左侧部分中)时,它呈现为文本而不是确切的 HTML 元素(看起来像在右边部分)。
这是我的代码示例:
import React, { useMemo, useState, useCallback, useRef, useSelected, useFocused } from "react"
import { createEditor, Transforms } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { jsx } from 'slate-hyperscript';
import { withHistory } from 'slate-history'
let qubicsStyle = [
{
color: "white",
border: "1px solid white",
borderRadius: "50%",
padding: "20px",
backgroundColor: "skyblue",
margin: "auto"
},
{
width: "0px",
height: "0px",
borderRight: "40px solid transparent",
borderTop: "40px solid red",
borderLeft: "40px solid red",
borderBottom: "40px solid red",
borderTopLeftRadius: "40px",
borderTopRightRadius: "40px",
borderBottomLeftRadius: "40px",
borderBottomRightRadius: "40px",
margin: "auto"
},
{
backgroundColor: "pink",
padding: "20px",
margin: "auto"
}
]
const initial = Array.from({ length: 5 }, (v, k) => k).map(k => {
var elStyle = qubicsStyle[Math.floor(Math.random() * qubicsStyle.length)];
const custom = {
id: `id-${k}`,
content: <div style={{ ...elStyle }}> Var {k}</div>
};
return custom;
});
const grid = 8;
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
const newDiv = removed;
const data = {
result,
newDiv
}
return data;
};
const getItemStyle = (isDragging, draggableStyle) => ({
margin: `0 0 ${grid}px 0`,
width: "200px",
border: "1px solid grey",
textAlign: "center",
background: isDragging ? "lightgreen" : "lightgray",
...draggableStyle
});
const Quote = ({ quote, index }) => {
return (
<Draggable draggableId={quote.id} index={index}>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
{quote.content}
</div>
)
}}
</Draggable >
);
}
const QuoteList = React.memo(function QuoteList({ quotes }) {
return quotes.map((quote, index) => (
<Quote quote={quote} index={index} key={quote.id} />
));
});
const Element = props => {
const { attributes, children, element } = props
console.log("Elemenmt props", props)
console.log("Type ===>", element.type)
switch (element.type) {
default:
return <p {...attributes}>{children}</p>
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':
return (
<a href={element.url} {...attributes}>
{children}
</a>
)
case 'div':
//currently working on this part - div rendering issue
return (
<div {...attributes}>
{children}
</div>
)
case 'image':
return <ImageElement {...props} />
}
}
const ImageElement = ({ attributes, children, element }) => {
const selected = useSelected()
const focused = useFocused()
const imageStyle = () => ({
display: `block`,
maxWidth: `100%`,
maxHeight: `20em`,
boxShadow: `${selected && focused ? '0 0 0 2px blue;' : 'none'}`
})
return (
<div {...attributes}>
{children}
<img
src={element.url}
style={imageStyle()}
/>
</div>
)
}
const Leaf = ({ attributes, children, leaf, provided }) => {
if (leaf.bold) {
children = <strong>{children}</strong>
}
if (leaf.code) {
children = <code>{children}</code>
}
if (leaf.italic) {
children = <em>{children}</em>
}
if (leaf.underline) {
children = <u>{children}</u>
}
if (leaf.strikethrough) {
children = <del>{children}</del>
}
return <span {...attributes} >{children}</span>
}
const withHtml = editor => {
const { insertData, isInline, isVoid } = editor
editor.isInline = element => {
return element.type === 'link' ? true : isInline(element)
}
editor.isVoid = element => {
return element.type === 'image' ? true : isVoid(element)
}
editor.insertData = data => {
const html = data.getData('text/html')
if (html) {
const parsed = new DOMParser().parseFromString(html, 'text/html')
const fragment = deserialize(parsed.body)
Transforms.insertFragment(editor, fragment)
return
}
insertData(data)
}
return editor
}
const ELEMENT_TAGS = {
A: el => ({ type: 'link', url: el.getAttribute('href') }),
BLOCKQUOTE: () => ({ type: 'quote' }),
H1: () => ({ type: 'heading-one' }),
H2: () => ({ type: 'heading-two' }),
H3: () => ({ type: 'heading-three' }),
H4: () => ({ type: 'heading-four' }),
H5: () => ({ type: 'heading-five' }),
H6: () => ({ type: 'heading-six' }),
IMG: el => ({ type: 'image', url: el.getAttribute('src') }),
LI: () => ({ type: 'list-item' }),
OL: () => ({ type: 'numbered-list' }),
P: () => ({ type: 'paragraph' }),
PRE: () => ({ type: 'code' }),
UL: () => ({ type: 'bulleted-list' }),
DIV: () => ({ type: 'div' }),
}
// COMPAT: `B` is omitted here because Google Docs uses `<b>` in weird ways.
const TEXT_TAGS = {
CODE: () => ({ code: true }),
DEL: () => ({ strikethrough: true }),
EM: () => ({ italic: true }),
I: () => ({ italic: true }),
S: () => ({ strikethrough: true }),
STRONG: () => ({ bold: true }),
U: () => ({ underline: true }),
}
export const deserialize = el => {
if (el.nodeType === 3) {
return el.textContent
} else if (el.nodeType !== 1) {
return null
} else if (el.nodeName === 'BR') {
return '\n'
}
const { nodeName } = el
let parent = el
if (
nodeName === 'PRE' &&
el.childNodes[0] &&
el.childNodes[0].nodeName === 'CODE'
) {
parent = el.childNodes[0]
}
const children = Array.from(parent.childNodes)
.map(deserialize)
.flat()
if (el.nodeName === 'BODY') {
return jsx('fragment', {}, children)
}
if (ELEMENT_TAGS[nodeName]) {
const attrs = ELEMENT_TAGS[nodeName](el)
return jsx('element', attrs, children)
}
if (TEXT_TAGS[nodeName]) {
const attrs = TEXT_TAGS[nodeName](el)
return children.map(child => jsx('text', attrs, child))
}
return children
}
const App = () => {
const editor = useMemo(
() => withHtml(withReact(withHistory(createEditor()))),
[]
)
const renderElement = useCallback(props => <Element {...props} />, [])
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const [state, setState] = useState({ quotes: initial });
const onDragEnd = (result) => {
if (!result.destination) {
return;
}
// if (result.destination.index === result.source.index) {
// return;
// }
const quotesData = reorder(
state.quotes,
result.source.index,
result.destination.index
);
const quotes = quotesData.result;
console.log("quotesData", quotesData)
const newV = JSON.stringify(quotesData.newDiv);
const pragraphValue = [...value, { type: 'div', children: [{ text: newV }] }];
setState({ quotes });
console.log("pragraphValue", pragraphValue)
setValue(pragraphValue);
}
console.log("---value", value)
return (
<>
<div style={{
border: "1px solid grey",
}}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="list">
{provided => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
<div style={{
display: "inline-block",
verticalAlign: "top",
width: "70%",
height: "500px",
padding: "20px",
border: "1px solid grey",
margin: "20px"
}}
>
<Slate
editor={editor}
value={value}
onChange={newValue => {
setValue(newValue)
}}
>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
placeholder="Paste in some HTML..."
style={{ backgroundColor: 'red' }}
/>
</Slate>
</div>
<div style={{
display: "inline-block",
verticalAlign: "top",
margin: "20px",
border: "1px solid grey",
padding: "20px",
height: "500px",
}}>
<div >
<QuoteList quotes={state.quotes} />
{provided.placeholder}
</div>
</div>
</div>
)}
</Droppable>
</DragDropContext>
</div>
</>
)
}
export default App
在拖动结束 (onDragEnd) 事件中,我已将拖放的 HTML 元素设置为 slate 的值,但它无法正确呈现。
谁能指导我如何在编辑器中呈现 HTML div 元素?
提前致谢 !!