123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 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 (
- <a href={url}>
- {props.children}
- </a>
- )
- }
- // 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) => (<span style={{ color: 'red' }}>[Emoji]</span>)
- component: (props: any) => (
- <i style={{
- display:'inline-block',
- height:'18px',
- width:'18px',
- backgroundImage:`url(https://i.pinimg.com/originals/03/7e/79/037e79b2fb52127537be79110891ae3f.png)`,
- backgroundSize:'100% 100%',
- color:'transparent'
- }}>{`e`}</i>)
- }
- ]);
-
- 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 (
- <div className={styles.wrapper}>
- <div className={styles.btnListWrapper}>
- <button onClick={this._onBoldClick.bind(this)}>Bold</button>
- <button onClick={this._onCheckRange.bind(this)}>Check</button>
- <button onClick={this._onLinkClick.bind(this)}>Link</button>
- <button onClick={(e: React.MouseEvent) => this._onEmojiClick(e, '0011')}>Emoji</button>
- </div>
- <div className={styles.editorWrapper}>
- <Editor
- editorState={editorState}
- blockStyleFn={this.defaultBlockStyleFn}
- blockRendererFn={this.defaultBlockRenderFn}
- handleKeyCommand={this.handleKeyCommand}
- onChange={this.onChange}
- />
- </div>
- </div>
- );
- }
- }
-
- export default RichTextEditor;
|