0

在此处输入图像描述

在此处输入图像描述

任务说明:

右侧部分包含可拖动的 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 元素?

提前致谢 !!

4

0 回答 0