import React from "react"; import PropTypes from "prop-types"; import { Editor, EditorState, DraftEditorCommand, RichUtils, Modifier, ContentState, convertToRaw, ContentBlock, CompositeDecorator, Entity, SelectionState, AtomicBlockUtils } from "draft-js"; import styles from "./RichTextEditor.less"; interface RichTextEditorProps {} interface RichTextEditorState { editorState: EditorState; } // 自定义组件,用于超链接 const Link = (props: any) => { // 这里通过contentState来获取entity�,之后通过getData获取entity中包含的数据 const { url } = props.contentState.getEntity(props.entityKey).getData(); return ( {props.children} ) } // decorator,用于超链接 const decorator = new CompositeDecorator([ { strategy (contentBlock, callback, contentState) { // 这个方法接收2个函数作为参数,如果第一个参数的函数执行时�返回true,就会执行第二个参数函数,同时会�将匹配的�字符的起始位置和结束位置传递给第二个参数。 contentBlock.findEntityRanges( (character) => { const entityKey = character.getEntity(); return ( entityKey !== null && contentState.getEntity(entityKey).getType() === 'LINK' ); }, (...arr) => { callback(...arr) } ); }, component: Link }, { strategy (contentBlock, callback, contentState) { contentBlock.findEntityRanges( (character) => { const entityKey = character.getEntity(); return ( entityKey !== null && contentState.getEntity(entityKey).getType() === 'EMOJI' ); }, (...arr) => { callback(...arr); } ) }, // component: (props: any) => ([Emoji]) component: (props: any) => ( {`e`}) } ]); class RichTextEditor extends React.Component< RichTextEditorProps, RichTextEditorState > { constructor(props: RichTextEditorProps) { super(props); this.state = { editorState: EditorState.createEmpty(decorator) }; this.onChange = (editorState: EditorState) => this.setState({ editorState }); this.handleKeyCommand = this.handleKeyCommand.bind(this); this.defaultBlockStyleFn = this.defaultBlockStyleFn.bind(this); } onChange: (editorState: EditorState) => void; handleKeyCommand(command: DraftEditorCommand, editorState: EditorState) { const newState = RichUtils.handleKeyCommand(editorState, command); console.log('command: ', command); console.log('newState: ', newState); if (newState) { this.onChange(newState); return "handled"; } switch (command) { case 'backspace': const contentState = editorState.getCurrentContent(); const selectionState = editorState.getSelection(); const [startOffset, endOffset] = [selectionState.getStartOffset(), selectionState.getEndOffset()]; if (startOffset === endOffset) { // 未选中状态 console.log(selectionState.getAnchorKey()); } // 选中状态 break; case 'backspace-word': case 'backspace-to-start-of-line': break; } return "not-handled"; } _onBoldClick() { this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD')); } _onLinkClick() { const { editorState } = this.state; const contentState = editorState.getCurrentContent(); const selectionState = editorState.getSelection(); const contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'http://www.zombo.com', }); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const contentStateWithLink = Modifier.applyEntity( contentStateWithEntity, selectionState, entityKey, ); const newEditorState = EditorState.push(editorState, contentStateWithLink, 'apply-entity'); this.onChange(newEditorState); } _onEmojiClick(e: React.MouseEvent, emojiCode: string) { const { editorState } = this.state; const contentState = editorState.getCurrentContent(); const selectionState = editorState.getSelection(); const EMOJIEntity: ContentState = contentState.createEntity('EMOJI', 'IMMUTABLE', { emojiCode }); const entityKey = EMOJIEntity.getLastCreatedEntityKey(); const ncsWithEntity = Modifier.insertText(contentState, selectionState, 'e', undefined, entityKey); const newEditorState = EditorState.push(editorState, ncsWithEntity, 'insert-characters'); // const newEditorState = AtomicBlockUtils.insertAtomicBlock( // editorState, // entityKey, // ' ' // ); this.onChange(newEditorState); } _onCheckRange() { console.log(this.state.editorState.getCurrentContent()); console.log(convertToRaw(this.state.editorState.getCurrentContent())); } defaultBlockStyleFn(contentBlock: ContentBlock) { const type = contentBlock.getType(); return ""; } defaultBlockRenderFn(contentBlock: ContentBlock) { const type = contentBlock.getType(); return contentBlock; } render() { const { editorState } = this.state; return (