No Description

index.tsx 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import React from "react";
  2. import PropTypes from "prop-types";
  3. import { Editor, EditorState, DraftEditorCommand, RichUtils, Modifier, ContentState, convertToRaw, ContentBlock, CompositeDecorator, Entity, SelectionState, AtomicBlockUtils } from "draft-js";
  4. import styles from "./RichTextEditor.less";
  5. interface RichTextEditorProps {}
  6. interface RichTextEditorState {
  7. editorState: EditorState;
  8. }
  9. // 自定义组件,用于超链接
  10. const Link = (props: any) => {
  11. // 这里通过contentState来获取entity�,之后通过getData获取entity中包含的数据
  12. const { url } = props.contentState.getEntity(props.entityKey).getData();
  13. return (
  14. <a href={url}>
  15. {props.children}
  16. </a>
  17. )
  18. }
  19. // decorator,用于超链接
  20. const decorator = new CompositeDecorator([
  21. {
  22. strategy (contentBlock, callback, contentState) {
  23. // 这个方法接收2个函数作为参数,如果第一个参数的函数执行时�返回true,就会执行第二个参数函数,同时会�将匹配的�字符的起始位置和结束位置传递给第二个参数。
  24. contentBlock.findEntityRanges(
  25. (character) => {
  26. const entityKey = character.getEntity();
  27. return (
  28. entityKey !== null &&
  29. contentState.getEntity(entityKey).getType() === 'LINK'
  30. );
  31. }, (...arr) => {
  32. callback(...arr)
  33. }
  34. );
  35. },
  36. component: Link
  37. },
  38. {
  39. strategy (contentBlock, callback, contentState) {
  40. contentBlock.findEntityRanges(
  41. (character) => {
  42. const entityKey = character.getEntity();
  43. return (
  44. entityKey !== null &&
  45. contentState.getEntity(entityKey).getType() === 'EMOJI'
  46. );
  47. }, (...arr) => {
  48. callback(...arr);
  49. }
  50. )
  51. },
  52. // component: (props: any) => (<span style={{ color: 'red' }}>[Emoji]</span>)
  53. component: (props: any) => (
  54. <i style={{
  55. display:'inline-block',
  56. height:'18px',
  57. width:'18px',
  58. backgroundImage:`url(https://i.pinimg.com/originals/03/7e/79/037e79b2fb52127537be79110891ae3f.png)`,
  59. backgroundSize:'100% 100%',
  60. color:'transparent'
  61. }}>{`e`}</i>)
  62. }
  63. ]);
  64. class RichTextEditor extends React.Component<
  65. RichTextEditorProps,
  66. RichTextEditorState
  67. > {
  68. constructor(props: RichTextEditorProps) {
  69. super(props);
  70. this.state = {
  71. editorState: EditorState.createEmpty(decorator)
  72. };
  73. this.onChange = (editorState: EditorState) =>
  74. this.setState({ editorState });
  75. this.handleKeyCommand = this.handleKeyCommand.bind(this);
  76. this.defaultBlockStyleFn = this.defaultBlockStyleFn.bind(this);
  77. }
  78. onChange: (editorState: EditorState) => void;
  79. handleKeyCommand(command: DraftEditorCommand, editorState: EditorState) {
  80. const newState = RichUtils.handleKeyCommand(editorState, command);
  81. console.log('command: ', command);
  82. console.log('newState: ', newState);
  83. if (newState) {
  84. this.onChange(newState);
  85. return "handled";
  86. }
  87. switch (command) {
  88. case 'backspace':
  89. const contentState = editorState.getCurrentContent();
  90. const selectionState = editorState.getSelection();
  91. const [startOffset, endOffset] = [selectionState.getStartOffset(), selectionState.getEndOffset()];
  92. if (startOffset === endOffset) {
  93. // 未选中状态
  94. console.log(selectionState.getAnchorKey());
  95. }
  96. // 选中状态
  97. break;
  98. case 'backspace-word':
  99. case 'backspace-to-start-of-line':
  100. break;
  101. }
  102. return "not-handled";
  103. }
  104. _onBoldClick() {
  105. this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'));
  106. }
  107. _onLinkClick() {
  108. const { editorState } = this.state;
  109. const contentState = editorState.getCurrentContent();
  110. const selectionState = editorState.getSelection();
  111. const contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', {
  112. url: 'http://www.zombo.com',
  113. });
  114. const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
  115. const contentStateWithLink = Modifier.applyEntity(
  116. contentStateWithEntity,
  117. selectionState,
  118. entityKey,
  119. );
  120. const newEditorState = EditorState.push(editorState, contentStateWithLink, 'apply-entity');
  121. this.onChange(newEditorState);
  122. }
  123. _onEmojiClick(e: React.MouseEvent, emojiCode: string) {
  124. const { editorState } = this.state;
  125. const contentState = editorState.getCurrentContent();
  126. const selectionState = editorState.getSelection();
  127. const EMOJIEntity: ContentState = contentState.createEntity('EMOJI', 'IMMUTABLE', { emojiCode });
  128. const entityKey = EMOJIEntity.getLastCreatedEntityKey();
  129. const ncsWithEntity = Modifier.insertText(contentState, selectionState, 'e', undefined, entityKey);
  130. const newEditorState = EditorState.push(editorState, ncsWithEntity, 'insert-characters');
  131. // const newEditorState = AtomicBlockUtils.insertAtomicBlock(
  132. // editorState,
  133. // entityKey,
  134. // ' '
  135. // );
  136. this.onChange(newEditorState);
  137. }
  138. _onCheckRange() {
  139. console.log(this.state.editorState.getCurrentContent());
  140. console.log(convertToRaw(this.state.editorState.getCurrentContent()));
  141. }
  142. defaultBlockStyleFn(contentBlock: ContentBlock) {
  143. const type = contentBlock.getType();
  144. return "";
  145. }
  146. defaultBlockRenderFn(contentBlock: ContentBlock) {
  147. const type = contentBlock.getType();
  148. return contentBlock;
  149. }
  150. render() {
  151. const { editorState } = this.state;
  152. return (
  153. <div className={styles.wrapper}>
  154. <div className={styles.btnListWrapper}>
  155. <button onClick={this._onBoldClick.bind(this)}>Bold</button>
  156. <button onClick={this._onCheckRange.bind(this)}>Check</button>
  157. <button onClick={this._onLinkClick.bind(this)}>Link</button>
  158. <button onClick={(e: React.MouseEvent) => this._onEmojiClick(e, '0011')}>Emoji</button>
  159. </div>
  160. <div className={styles.editorWrapper}>
  161. <Editor
  162. editorState={editorState}
  163. blockStyleFn={this.defaultBlockStyleFn}
  164. blockRendererFn={this.defaultBlockRenderFn}
  165. handleKeyCommand={this.handleKeyCommand}
  166. onChange={this.onChange}
  167. />
  168. </div>
  169. </div>
  170. );
  171. }
  172. }
  173. export default RichTextEditor;