雖然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)}
/>
?
?