|
|
|
|
import { createContext, useEffect, useState } from 'react';
|
|
|
|
|
import ExampleTheme from "./themes/ExampleTheme";
|
|
|
|
|
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
|
|
|
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
|
|
|
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
|
|
|
|
|
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
|
|
|
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
|
|
|
|
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
|
|
|
|
|
import {LexicalErrorBoundary} from "@lexical/react/LexicalErrorBoundary";
|
|
|
|
|
import {TabIndentationPlugin} from '@lexical/react/LexicalTabIndentationPlugin';
|
|
|
|
|
import TreeViewPlugin from "./plugins/TreeViewPlugin";
|
|
|
|
|
import ToolbarPlugin from "./plugins/ToolbarPlugin";
|
|
|
|
|
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
|
|
|
|
|
import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
|
|
|
|
|
import { ListItemNode, ListNode } from "@lexical/list";
|
|
|
|
|
import { CodeHighlightNode, CodeNode } from "@lexical/code";
|
|
|
|
|
import { AutoLinkNode, LinkNode } from "@lexical/link";
|
|
|
|
|
// import {ClickableLinkPlugin} from '@lexical/react/LexicalClickableLinkPlugin';
|
|
|
|
|
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
|
|
|
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
|
|
|
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
|
|
|
|
import {HorizontalRulePlugin} from '@lexical/react/LexicalHorizontalRulePlugin';
|
|
|
|
|
import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode';
|
|
|
|
|
import { TRANSFORMERS } from "@lexical/markdown";
|
|
|
|
|
|
|
|
|
|
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
|
|
|
|
|
import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin";
|
|
|
|
|
import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
|
|
|
|
|
import TabFocusPlugin from './plugins/TabFocusPlugin';
|
|
|
|
|
import EditorRefPlugin from './plugins/EditorRefPlugin'
|
|
|
|
|
import ImagesPlugin from './plugins/ImagesPlugin';
|
|
|
|
|
import InlineImagePlugin from './plugins/InlineImagePlugin';
|
|
|
|
|
import { ImageNode } from './nodes/ImageNode';
|
|
|
|
|
import {InlineImageNode} from './nodes/InlineImageNode/InlineImageNode';
|
|
|
|
|
|
|
|
|
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
|
|
|
// import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
|
|
|
|
|
|
|
|
|
|
import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
|
|
|
|
|
import {useLexicalEditable} from '@lexical/react/useLexicalEditable';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { $getRoot, $getSelection, $createParagraphNode } from 'lexical';
|
|
|
|
|
import { $generateHtmlFromNodes, $generateNodesFromDOM, } from '@lexical/html';
|
|
|
|
|
// import { } from '@lexical/clipboard';
|
|
|
|
|
import { isEmpty } from '@/utils/commons';
|
|
|
|
|
|
|
|
|
|
import './styles.css';
|
|
|
|
|
|
|
|
|
|
function Placeholder() {
|
|
|
|
|
return <div className="editor-placeholder">Enter some rich text...</div>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const editorConfig = {
|
|
|
|
|
// The editor theme
|
|
|
|
|
// theme: {},
|
|
|
|
|
theme: ExampleTheme,
|
|
|
|
|
// Handling of errors during update
|
|
|
|
|
onError(error) {
|
|
|
|
|
throw error;
|
|
|
|
|
},
|
|
|
|
|
// Any custom nodes go here
|
|
|
|
|
nodes: [
|
|
|
|
|
HeadingNode,
|
|
|
|
|
ListNode,
|
|
|
|
|
ListItemNode,
|
|
|
|
|
QuoteNode,
|
|
|
|
|
CodeNode,
|
|
|
|
|
CodeHighlightNode,
|
|
|
|
|
TableNode,
|
|
|
|
|
TableCellNode,
|
|
|
|
|
TableRowNode,
|
|
|
|
|
AutoLinkNode,
|
|
|
|
|
LinkNode,
|
|
|
|
|
HorizontalRuleNode,
|
|
|
|
|
ImageNode,InlineImageNode,
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function LexicalDefaultValuePlugin({ value = "" }= {}) {
|
|
|
|
|
const [editor] = useLexicalComposerContext();
|
|
|
|
|
|
|
|
|
|
const updateHTML = (editor, value, clear) => {
|
|
|
|
|
const root = $getRoot();
|
|
|
|
|
const parser = new DOMParser();
|
|
|
|
|
const dom = parser.parseFromString(value, "text/html");
|
|
|
|
|
const nodes = $generateNodesFromDOM(editor, dom);
|
|
|
|
|
if (clear) {
|
|
|
|
|
root.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const p = $createParagraphNode();
|
|
|
|
|
const _p = nodes.filter(n => n).forEach((n) => {
|
|
|
|
|
const paragraphNode = $createParagraphNode();
|
|
|
|
|
paragraphNode.append(n);
|
|
|
|
|
// p.append(paragraphNode);
|
|
|
|
|
root.append(paragraphNode);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// root.append(...nodes.filter(n => n));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 默认值设置只用初始化一次;
|
|
|
|
|
// 空值不更新 HTML;
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (editor && !isEmpty(value)) {
|
|
|
|
|
editor.update(() => {
|
|
|
|
|
updateHTML(editor, value, true);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
function MyOnChangePlugin({ ignoreHistoryMergeTagChange = true, ignoreSelectionChange = true, onChange }) {
|
|
|
|
|
const [editor] = useLexicalComposerContext();
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (onChange) {
|
|
|
|
|
return editor.registerUpdateListener(({editorState, dirtyElements, dirtyLeaves, prevEditorState, tags}) => {
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
(ignoreSelectionChange &&
|
|
|
|
|
dirtyElements.size === 0 &&
|
|
|
|
|
dirtyLeaves.size === 0) ||
|
|
|
|
|
(ignoreHistoryMergeTagChange && tags.has('history-merge')) ||
|
|
|
|
|
prevEditorState.isEmpty()
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// const editorStateJSON = editorState.toJSON();
|
|
|
|
|
let html;
|
|
|
|
|
let textContent;
|
|
|
|
|
editorState.read(() => {
|
|
|
|
|
const root = $getRoot();
|
|
|
|
|
const textContent = root.getTextContent();
|
|
|
|
|
// console.log('textContent', textContent);
|
|
|
|
|
|
|
|
|
|
const html = $generateHtmlFromNodes(editor);
|
|
|
|
|
// console.log('html', html);
|
|
|
|
|
|
|
|
|
|
// setEditorContent(content);
|
|
|
|
|
if (typeof onChange === 'function') {
|
|
|
|
|
onChange({ editorState, editor, tags, html, textContent });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, [editor, ignoreHistoryMergeTagChange, ignoreSelectionChange, onChange]);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
export default function Editor({ isRichText, isDebug, editorRef, onChange, initialValue, ...props }) {
|
|
|
|
|
return (
|
|
|
|
|
<LexicalComposer initialConfig={editorConfig}>
|
|
|
|
|
<div className='editor-container'>
|
|
|
|
|
{isRichText && <ToolbarPlugin />}
|
|
|
|
|
<div className='editor-inner'>
|
|
|
|
|
{/* <LexicalPlainText /> */}
|
|
|
|
|
{isRichText ? (
|
|
|
|
|
<RichTextPlugin contentEditable={<ContentEditable className='editor-input' />} placeholder={<Placeholder />} ErrorBoundary={LexicalErrorBoundary} />
|
|
|
|
|
) : (
|
|
|
|
|
<PlainTextPlugin contentEditable={<ContentEditable className='editor-pure-input' />} ErrorBoundary={LexicalErrorBoundary} />
|
|
|
|
|
)}
|
|
|
|
|
<HistoryPlugin />
|
|
|
|
|
{(import.meta.env.DEV && isDebug) && <TreeViewPlugin />}
|
|
|
|
|
<LexicalDefaultValuePlugin value={initialValue} />
|
|
|
|
|
<AutoFocusPlugin />
|
|
|
|
|
<CodeHighlightPlugin />
|
|
|
|
|
<ListPlugin />
|
|
|
|
|
<ListMaxIndentLevelPlugin maxDepth={7} />
|
|
|
|
|
<AutoLinkPlugin />
|
|
|
|
|
<LinkPlugin />
|
|
|
|
|
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
|
|
|
|
|
<TabFocusPlugin />
|
|
|
|
|
<TabIndentationPlugin />
|
|
|
|
|
<HorizontalRulePlugin />
|
|
|
|
|
<EditorRefPlugin editorRef={editorRef} />
|
|
|
|
|
<ImagesPlugin />
|
|
|
|
|
<InlineImagePlugin />
|
|
|
|
|
<MyOnChangePlugin onChange={onChange}/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</LexicalComposer>
|
|
|
|
|
);
|
|
|
|
|
}
|