基于Draftjs實現(xiàn)的Electron富文本聊天輸入框(五) —— 問題總結與解決

雖然draftjs是個Facebook推出的較為成熟的開源項目,但畢竟實際運用時的需求很多樣,而且引用了draft-js-mention-plugin這樣的插件。開發(fā)過程中,遇到不少問題随橘。

輸入框組件移除再切換回來,decorators和plugins不起作用锦庸,移除當前會話后重新打開則正常

分析:受影響的是draft plugins庫提供的plugins和decorators兩個屬性机蔗,draftjs提供的功能均正常;因此主要查看draft plugin的issue甘萧。

issue里有很多相關但都沒有準確解決方案的問題萝嘁,https://github.com/draft-js-plugins/draft-js-plugins/issues/251

draft plugin的Editor是在componentWillMount的方法里去注冊decorators,issue里提到注冊的時候需要正確的EditorState扬卷。修改如下:

let initialDraft = null;

// 根據(jù)issue, 這里需重設editorState牙言,否則出現(xiàn)decorators失效問題
componentWillMount() {
    if (!!this.props.draft) {
      initialDraft = this.props.draft;
      this.setStateWithDraft(this.props.draft);
    } else {
      this.setEmptyState();
    }
  }

componentWillReceiveProps(nextProps) {
    .......
    
    if (this.props.draft && nextProps.draft && initialDraft !== nextProps.draft) {
      this.setStateWithDraft(nextProps.draft);
    }
  }

在自定義的Editor組件里添加了對componentWillMount方法的重寫,通過EditorState.push去重設editorState怪得。同時記錄了Mount時的draft值咱枉,避免在componentWillReceiveProps重復賦值卑硫。

問題是解決了,但原因并不是很清楚

刪除換行蚕断,在輸入框顯示正常欢伏,但發(fā)送出去仍有'\r'

Draftjs在執(zhí)行刪除換行時,雖然解析出來的text顯示正常亿乳,但通過escape解析發(fā)現(xiàn)其text中原換行處仍有\(zhòng)r硝拧,即使在Draft輸入框顯示是正常的。

在Draftjs輸入框中葛假,\r\n才表現(xiàn)出換行障陶,因此發(fā)送事件在獲取輸入框內容時,可以全局替換\r符,在消息顯示處,無論有\(zhòng)r或\n都能表現(xiàn)出換行糖荒。替換后并不影響正常換行的樣式

輸入框滾輪不能保證光標位于可視區(qū)域

分析

draftjs項目有相關issue,當輸入框添加過decorator時鼓寺,滾輪無法隨內容auto scroll。

我們輸入框的滾輪由draftEditor外面的div控制遏暴,因此,可以通過在輸入框手動更新state的回調中去手動控制滾輪位置

方案

參考slate源碼指黎,主要通過selection的相關API拿到光標在輸入框中的相關位置:

setTimeout(()=> {
        this.focus();
        if (this.editor && this.container) {
          const editor = this.editor.editor.refs.editor;
          const selection = window.getSelection();

          if (selection && editor.scrollHeight > this.container.clientHeight) {
            const range = selection.getRangeAt(0).cloneRange();
            const rangeRect = range.getBoundingClientRect();
            const containerRect = this.container.getBoundingClientRect();
            console.log('editor rect', rangeRect, containerRect);

            if (rangeRect.top === 0) {
              this.container.scrollTop = editor.scrollHeight;
            } else {
              this.container.scrollTop += rangeRect.top - containerRect.top - rangeRect.height;
            }
          }
        }
      }, 100);

selection.getRangeAt(0).cloneRange().getBoundingClientRect()拿到光標所在節(jié)點的clientRect值朋凉。

setTimeOut()是考慮到插入圖片時,有個改變圖片大小的過程醋安。

mention插件導致輸入框上下鍵無反應

分析

draft-js-mention-plugin源碼中杂彭,mentionSuggestion組件對上下鍵做了監(jiān)聽處理,并且通過e.preventDefault()禁止了正常上下鍵事件吓揪。按道理來說亲怠,沒有@字符觸發(fā)時,該組件不存在柠辞,也不會觸發(fā)事件監(jiān)聽团秽。然而,初始化時叭首,即使沒有@,其組件也存在习勤,只是沒有顯示出來。這似乎是另外一個bug

解決

issue中有相關問題:https://github.com/draft-js-plugins/draft-js-plugins/pull/1002

但并沒有完全解決我們的問題

//node-modules/draft-js-mention-plugin/lib/MentionSuggestions/index.js

onDownArrow = (keyboardEvent) => {
    if (!this.state.isActive || document.getElementsByClassName('mention-suggestion').length === 0) return;
    keyboardEvent.preventDefault();
    const newIndex = this.state.focusedOptionIndex + 1;
    this.onMentionFocus(newIndex >= this.props.suggestions.size ? 0 : newIndex);
  };

onTab = (keyboardEvent) => {
    if (!this.state.isActive ) return;
    keyboardEvent.preventDefault();
    this.commitSelection();
  };

onUpArrow = (keyboardEvent) => {
    if (!this.state.isActive || document.getElementsByClassName('mention-suggestion').length === 0) return;
    keyboardEvent.preventDefault();
    if (this.props.suggestions.size > 0) {
      const newIndex = this.state.focusedOptionIndex - 1;
      this.onMentionFocus(newIndex < 0 ? this.props.suggestions.size - 1 : newIndex);
    }
  };

輸入框初始化就會觸發(fā)mentionSuggestions的openDropdown()方法焙格,導致isActive的值為true图毕,直接通過isActive無法進行控制,后使用this.props.store.getIsOpened()的值判斷是否彈出了mention列表眷唉。

后解決如下:

onDownArrow = (keyboardEvent) => {
    if (!this.props.store.getIsOpened()) return;
    keyboardEvent.preventDefault();
    const newIndex = this.state.focusedOptionIndex + 1;
    this.onMentionFocus(newIndex >= this.props.suggestions.size ? 0 : newIndex);
  };

onTab = (keyboardEvent) => {
    if (!this.props.store.getIsOpened()) return;
    keyboardEvent.preventDefault();
    this.commitSelection();
  };

onUpArrow = (keyboardEvent) => {
    if (!this.props.store.getIsOpened()) return;
    keyboardEvent.preventDefault();
    if (this.props.suggestions.size > 0) {
      const newIndex = this.state.focusedOptionIndex - 1;
      this.onMentionFocus(newIndex < 0 ? this.props.suggestions.size - 1 : newIndex);
    }
  };

@后面緊跟非空白字符時予颤,選擇后的@文本會取代后面的所有文本

分析

定位相關邏輯:

// draft-js-mention-plugin/src/modifiers/addMention.js
const addMention = (editorState, mention, mentionPrefix, mentionTrigger, entityMutability) => {
  ......

  const currentSelectionState = editorState.getSelection();
  const { begin, end } = getSearchText(editorState, currentSelectionState, mentionTrigger);

  // get selection of the @mention search text
  const mentionTextSelection = currentSelectionState.merge({
    anchorOffset: begin,
    focusOffset: end,
  });

  let mentionReplacedContent = Modifier.replaceText(
    editorState.getCurrentContent(),
    mentionTextSelection,
    `${mentionPrefix}${mention.name}`,
    null, // no inline style needed
    entityKey
  );

  // If the mention is inserted at the end, a space is appended right after for
  // a smooth writing experience.
  const blockKey = mentionTextSelection.getAnchorKey();
  const blockSize = editorState.getCurrentContent().getBlockForKey(blockKey).getLength();
  if (blockSize === end) {
    mentionReplacedContent = Modifier.insertText(
      mentionReplacedContent,
      mentionReplacedContent.getSelectionAfter(),
      ' ',
    );
  }
......
};

上述代碼執(zhí)行用戶選擇了@文本后囤官,mention插件將該文本插入到當前editorState的過程。debug發(fā)現(xiàn)蛤虐,當@后面緊跟非空白符時党饮,currentSelectionState覆蓋的范圍從@直到整個文本結尾,而不僅僅是@字符..因此在Modifier.replaceText 后笆焰,@文本替換了整個文本劫谅。

解決

雖然問題分析下來,原因似乎出在selectionState上嚷掠,但這并不好改捏检,也不應該改..畢竟,我們本來就不應該讓@后面緊跟非空白符時也觸發(fā)不皆,于是贯城,查看mention插件的trigger邏輯:

// draft-js-mention-plugin/src/mentionSuggestionsStrategy.js

import findWithRegex from 'find-with-regex';
import escapeRegExp from 'lodash.escaperegexp';

export default (trigger: string, regExp: string) => (contentBlock: Object, callback: Function) => {
  const reg = new RegExp(String.raw({
    raw: `(\\s|^)${escapeRegExp(trigger)}${regExp}` // eslint-disable-line no-useless-escape
  }), 'g');
  findWithRegex(reg, contentBlock, callback);
};

trigger和regExp是我們傳進來的,我們只用到trigger:@霹娄,regExp沒用到就忽略了..即正則為:/(\s|^)@/g能犯,匹配的是以@開頭或以空白符+@開始,那為了滿足我們的需求犬耻,修改為/(\s|^)@(\s|$)/g踩晶,對應源碼:

const reg = new RegExp(String.raw({
    raw: `(\\s|^)${escapeRegExp(trigger)}${regExp}$(\\s|$)` // eslint-disable-line no-useless-escape
  }), 'g');

tab鍵問題

分析

draftjs只在UL/OL中對tab鍵做了處理,其他情況下tab鍵觸發(fā)瀏覽器默認行為枕磁,即focus到頁面中下一個輸入框或可focus的元素渡蜻。同時其提供的API:keyBindingFn也監(jiān)聽不到tab鍵。

解決

在draftEditor外層的div中添加onKeyDown監(jiān)聽:

handleKeyEvent(e) {
    if (e.keyCode === 9) {
      // 插入tab制表符
      e.preventDefault();
      this.appendContent('\t', 'insert-characters');
    }
  }

<div
  onKeyDown={this.handleKeyEvent.bind(this)}>
</div>

拖動txt文件至輸入框問題

分析

在拖動txt格式文件至輸入框時计济,文件的內容被讀取和插入到輸入框中茸苇。Drag相關事件我們是在父組件上處理的,封裝的Draft組件本身沒有對drag做什么處理沦寂,也沒有相關props学密。因此,分析是draft本身的處理機制導致传藏。

// draft-js/src/component/handlers/drag/DraftEditorDragHandler.js
/**
 * Handle data being dropped.
 */
  onDrop: function(editor: DraftEditor, e: Object): void {
    const data = new DataTransfer(e.nativeEvent.dataTransfer);

    const editorState: EditorState = editor._latestEditorState;
    const dropSelection: ?SelectionState = getSelectionForEvent(
      e.nativeEvent,
      editorState,
    );

    e.preventDefault();
    editor.exitCurrentMode();

    if (dropSelection == null) {
      return;
    }

    const files = data.getFiles();
    if (files.length > 0) {
      if (
        editor.props.handleDroppedFiles &&
        isEventHandled(editor.props.handleDroppedFiles(dropSelection, files))
      ) {
        return;
      }

      getTextContentFromFiles(files, fileText => {
        fileText &&
          editor.update(
            insertTextAtSelection(editorState, dropSelection, fileText),
          );
      });
      return;
    }

上述代碼是draft對拖入文件的處理部分腻暮,getTextContentFromFiles是其內部封裝的讀取文件內容方法,當讀取到文本內容時毯侦,draft會將其插入輸入框光標處西壮。

為了避免走到這段邏輯,需要editor.props.handleDroppedFile && isEventHandled(editor.props.handleDroppedFiles(dropSelection, files))叫惊。

解決

添加prop方法handleDroppedFiles并且返回handled;

handleDroppedFiles(selection, files) {
    if (files) {
      // 不進入draft的默認邏輯
      return 'handled';
    }
  }
  
<Editor
    handleDroppedFiles={this.handleDroppedFiles.bind(this)}
/>    

?

?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末款青,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霍狰,更是在濱河造成了極大的恐慌抡草,老刑警劉巖饰及,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異康震,居然都是意外死亡燎含,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門腿短,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屏箍,“玉大人,你說我怎么就攤上這事橘忱「翱” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵钝诚,是天一觀的道長颖御。 經(jīng)常有香客問我,道長凝颇,這世上最難降的妖魔是什么潘拱? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拧略,結果婚禮上芦岂,老公的妹妹穿的比我還像新娘。我一直安慰自己垫蛆,他們只是感情好禽最,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著月褥,像睡著了一般弛随。 火紅的嫁衣襯著肌膚如雪瓢喉。 梳的紋絲不亂的頭發(fā)上宁赤,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音栓票,去河邊找鬼决左。 笑死,一個胖子當著我的面吹牛走贪,可吹牛的內容都是我干的佛猛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼坠狡,長吁一口氣:“原來是場噩夢啊……” “哼继找!你這毒婦竟也來了?” 一聲冷哼從身側響起逃沿,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤婴渡,失蹤者是張志新(化名)和其女友劉穎幻锁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體边臼,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哄尔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柠并。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岭接。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖臼予,靈堂內的尸體忽然破棺而出鸣戴,到底是詐尸還是另有隱情,我是刑警寧澤瘟栖,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布葵擎,位于F島的核電站,受9級特大地震影響半哟,放射性物質發(fā)生泄漏酬滤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一寓涨、第九天 我趴在偏房一處隱蔽的房頂上張望盯串。 院中可真熱鬧,春花似錦戒良、人聲如沸体捏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽几缭。三九已至,卻和暖如春沃呢,著一層夾襖步出監(jiān)牢的瞬間年栓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工薄霜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留某抓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓惰瓜,卻偏偏與公主長得像否副,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崎坊,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容