Просмотр исходного кода

WIP: refactor EmojiPickerHelper

Yuki Takei 3 лет назад
Родитель
Сommit
08faf7b375

+ 21 - 67
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -7,7 +7,7 @@ import * as loadCssSync from 'load-css-file';
 import PropTypes from 'prop-types';
 import { Button } from 'reactstrap';
 import * as loadScript from 'simple-load-script';
-import { throttle } from 'throttle-debounce';
+import { throttle, debounce } from 'throttle-debounce';
 import urljoin from 'url-join';
 
 import InterceptorManager from '~/services/interceptor-manager';
@@ -136,18 +136,13 @@ class CodeMirrorEditor extends AbstractEditor {
     this.changeHandler = this.changeHandler.bind(this);
     this.turnOnEmojiPickerMode = this.turnOnEmojiPickerMode.bind(this);
     this.turnOffEmojiPickerMode = this.turnOffEmojiPickerMode.bind(this);
-    // this.resetEmojiSearchText = this.resetEmojiSearchText.bind(this);
-    // this.isCharValidForShowingEmojiPicker = this.isCharValidForShowingEmojiPicker.bind(this);
-    // this.loadEmojiSearchText = this.loadEmojiSearchText.bind(this);
-    // this.resetEmojiPickerState = this.resetEmojiPickerState.bind(this);
     this.windowClickHandler = this.windowClickHandler.bind(this);
     this.clickHandlerForEmojiPicker = this.clickHandlerForEmojiPicker.bind(this);
     this.keyDownHandler = this.keyDownHandler.bind(this);
     this.keyDownHandlerForEmojiPicker = this.keyDownHandlerForEmojiPicker.bind(this);
     this.showEmojiPicker = this.showEmojiPicker.bind(this);
-    // this.loadEmojiPicker = this.loadEmojiPicker.bind(this);
     this.keyPressHandlerForEmojiPicker = this.keyPressHandlerForEmojiPicker.bind(this);
-    this.keyPressHandlerForEmojiPickerThrottled = throttle(400, this.keyPressHandlerForEmojiPicker);
+    this.keyPressHandlerForEmojiPickerThrottled = debounce(50, throttle(400, this.keyPressHandlerForEmojiPicker));
     this.keyPressHandler = this.keyPressHandler.bind(this);
 
     this.updateCheatsheetStates = this.updateCheatsheetStates.bind(this);
@@ -610,94 +605,52 @@ class CodeMirrorEditor extends AbstractEditor {
     this.setState({
       isEmojiPickerMode: true,
       startPosWithEmojiPickerModeTurnedOn: pos,
-      emojiSearchText: '',
     });
   }
 
   turnOffEmojiPickerMode() {
     this.setState({
       isEmojiPickerMode: false,
-      startPosWithEmojiPickerModeTurnedOn: null,
     });
   }
 
-  // resetEmojiSearchText() {
-  //   this.setState({
-  //     emojiSearchText: '',
-  //     startPosWithEmojiPickerModeTurnedOn: null,
-  //   });
-  // }
-
-  // isCharValidForShowingEmojiPicker(ch) {
-  //   const lowerCh = ch.toLowerCase();
-
-  //   return 'abcdefghijklmnopqrstuvwxyz0123456789-+_'.indexOf(lowerCh) !== -1;
-  // }
-
-  showEmojiPicker(emojiSearchText) {
+  showEmojiPicker(initialSearchingText) {
     // show emoji picker with a stored word
     this.setState({
       isEmojiPickerShown: true,
-      emojiSearchText,
+      emojiSearchText: initialSearchingText ?? '',
     });
-    this.turnOffEmojiPickerMode();
-  }
-
-  // loadEmojiPicker(ch) {
-  //   this.loadEmojiSearchText(ch);
 
-  //   this.showEmojiPicker();
-  // }
-
-  // evalToOpenEmojiPicker(currentPos) {
-  // }
+    const resetStartPos = initialSearchingText == null;
+    if (resetStartPos) {
+      this.setState({ startPosWithEmojiPickerModeTurnedOn: null });
+    }
 
-  // resetEmojiPickerState() {
-  //   this.offEmojiPickerMode();
-  //   this.resetEmojiSearchText();
-  // }
+    this.turnOffEmojiPickerMode();
+  }
 
   keyPressHandlerForEmojiPicker(editor, event) {
     const char = event.key;
     const isEmojiPickerMode = this.state.isEmojiPickerMode;
 
     if (!isEmojiPickerMode) {
-      if (char === ':') { // Turn on emoji picker mode
-        const currentPos = editor.getCursor();
-        this.turnOnEmojiPickerMode(currentPos);
+      const startPos = this.emojiPickerHelper.shouldModeTurnOn(char);
+      if (startPos != null) { // Turn on emoji picker mode
+        this.turnOnEmojiPickerMode(startPos);
         return;
       }
 
       return;
     }
 
-    const currentPos = editor.getCursor();
-    const rangeStr = editor.getRange(this.state.startPosWithEmojiPickerModeTurnedOn, currentPos);
-
-    const pattern = new RegExp(/^:[a-z0-9-+_]+$/);
-    if (pattern.test(rangeStr)) {
-      const initialSearchingText = rangeStr.slice(1); // omit heading ':'
+    const startPos = this.state.startPosWithEmojiPickerModeTurnedOn;
+    if (this.emojiPickerHelper.shouldOpen(startPos)) {
+      const initialSearchingText = this.emojiPickerHelper.getInitialSearchingText(startPos);
       this.showEmojiPicker(initialSearchingText);
+      return;
     }
 
-    // const sc = editor.getSearchCursor(':', currentPos, { multiline: false });
-    // if (sc.findPrevious()) {
-    //   console.log({ currentPos, sc });
-    // }
-
-    // return sc;
-
-    // Return not to reset emoji picker state when pressing : many times
-    // if (char === ':') {
-    //   return;
-    // }
-
-    // if (!this.isCharValidForShowingEmojiPicker(char)) {
-    //   this.resetEmojiPickerState();
-    //   return;
-    // }
-
-    // this.loadEmojiPicker(char);
+    this.turnOffEmojiPickerMode();
   }
 
   keyPressHandler(editor, event) {
@@ -824,7 +777,8 @@ class CodeMirrorEditor extends AbstractEditor {
         <div className="text-left">
           <div className="mb-2 d-none d-md-block">
             <EmojiPicker
-              onClose={() => this.setState({ isEmojiPickerShown: false, emojiSearchText: null })}
+              onClose={() => this.setState({ isEmojiPickerShown: false })}
+              onSelected={emoji => this.emojiPickerHelper.addEmoji(emoji, this.state.startPosWithEmojiPickerModeTurnedOn)}
               emojiSearchText={emojiSearchText}
               emojiPickerHelper={this.emojiPickerHelper}
               isOpen={this.state.isEmojiPickerShown}
@@ -1078,7 +1032,7 @@ class CodeMirrorEditor extends AbstractEditor {
         color={null}
         bssize="small"
         title="Emoji"
-        onClick={() => this.setState({ isEmojiPickerShown: true })}
+        onClick={() => this.showEmojiPicker()}
       >
         <EditorIcon icon="Emoji" />
       </Button>,

+ 9 - 13
packages/app/src/components/PageEditor/EmojiPicker.tsx

@@ -1,4 +1,4 @@
-import React, { FC } from 'react';
+import React, { FC, useCallback } from 'react';
 
 import { Picker } from 'emoji-mart';
 import { Modal } from 'reactstrap';
@@ -9,6 +9,7 @@ import EmojiPickerHelper, { getEmojiTranslation } from './EmojiPickerHelper';
 
 type Props = {
   onClose: () => void,
+  onSelected: (emoji: string) => void,
   emojiSearchText: string,
   emojiPickerHelper: EmojiPickerHelper,
   isOpen: boolean
@@ -17,30 +18,25 @@ type Props = {
 const EmojiPicker: FC<Props> = (props: Props) => {
 
   const {
-    onClose, emojiSearchText, emojiPickerHelper, isOpen,
+    onClose, onSelected, emojiSearchText, emojiPickerHelper, isOpen,
   } = props;
 
   const { resolvedTheme } = useNextThemes();
 
   // Set search emoji input and trigger search
-  const searchEmoji = () => {
+  const searchEmoji = useCallback(() => {
     const input = window.document.querySelector('[id^="emoji-mart-search"]') as HTMLInputElement;
     const valueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
     valueSetter?.call(input, emojiSearchText);
     const event = new Event('input', { bubbles: true });
     input.dispatchEvent(event);
     input.focus();
-  };
-
-  const selectEmoji = (emoji) => {
-    if (emojiSearchText !== null) {
-      emojiPickerHelper.addEmojiOnSearch(emoji);
-    }
-    else {
-      emojiPickerHelper.addEmoji(emoji);
-    }
+  }, [emojiSearchText]);
+
+  const selectEmoji = useCallback((emoji) => {
+    onSelected(emoji);
     onClose();
-  };
+  }, [onClose, onSelected]);
 
 
   const translation = getEmojiTranslation();

+ 30 - 20
packages/app/src/components/PageEditor/EmojiPickerHelper.ts

@@ -1,10 +1,10 @@
 import { CSSProperties } from 'react';
 
+import { Position } from 'codemirror';
 import i18n from 'i18next';
 
-// https://regex101.com/r/Gqhor8/1
-// const EMOJI_PATTERN = new RegExp(/\B:[^:\s]+/);
-const EMOJI_PATTERN = ':';
+// https://regex101.com/r/bMLnjG/1
+const EMOJI_PATTERN = new RegExp(/\B:[^:\s]+/);
 
 export default class EmojiPickerHelper {
 
@@ -14,10 +14,9 @@ export default class EmojiPickerHelper {
 
   constructor(editor) {
     this.editor = editor;
-    this.pattern = EMOJI_PATTERN;
   }
 
-  setStyle = ():CSSProperties => {
+  setStyle = (): CSSProperties => {
     const offset = 20;
     const emojiPickerHeight = 420;
     const cursorPos = this.editor.cursorCoords(true);
@@ -37,29 +36,40 @@ export default class EmojiPickerHelper {
     };
   };
 
-  getSearchCursor = () => {
-    const currentPos = this.editor.getCursor();
-    const sc = this.editor.getSearchCursor(this.pattern, currentPos, { multiline: false });
-    return sc;
-  };
+  shouldModeTurnOn = (char: string): Position | null | undefined => {
+    if (char !== ':') {
+      return null;
+    }
 
-  // Add emoji when triggered by search
-  addEmojiOnSearch = (emoji) => {
     const currentPos = this.editor.getCursor();
-    const sc = this.getSearchCursor();
+    const sc = this.editor.getSearchCursor(':', currentPos, { multiline: false }).pos;
     if (sc.findPrevious()) {
-      sc.replace(`${emoji.colons} `, this.editor.getTokenAt(currentPos).string);
-      this.editor.focus();
-      this.editor.refresh();
+      return sc.pos;
     }
-  };
+  }
+
+  shouldOpen = (startPos: Position): boolean => {
+    const currentPos = this.editor.getCursor();
+    const rangeStr = this.editor.getRange(startPos, currentPos);
 
+    return EMOJI_PATTERN.test(rangeStr);
+  }
 
-  // Add emoji when triggered by click emoji icon on top of editor
-  addEmoji = (emoji) => {
+  getInitialSearchingText = (startPos: Position): void => {
     const currentPos = this.editor.getCursor();
+    const rangeStr = this.editor.getRange(startPos, currentPos);
+
+    return rangeStr.slice(1); // return without the heading ':'
+  }
+
+  addEmoji = (emoji: { colons: string }, startPosToReplace: Position|null): void => {
+    const currentPos = this.editor.getCursor();
+
+    const from = startPosToReplace ?? currentPos;
+    const to = currentPos;
+
     const doc = this.editor.getDoc();
-    doc.replaceRange(`${emoji.colons} `, currentPos);
+    doc.replaceRange(`${emoji.colons} `, from, to);
     this.editor.focus();
     this.editor.refresh();
   };