diff --git a/src/Editor/index.js b/src/Editor/index.js index 08bf68d54..0cc783364 100644 --- a/src/Editor/index.js +++ b/src/Editor/index.js @@ -77,7 +77,7 @@ class WysiwygEditor extends Component { const newState = {}; const { editorState, contentState } = this.props; if (!this.state.toolbar) { - const toolbar = mergeRecursive(defaultToolbar, toolbar); + const toolbar = mergeRecursive(defaultToolbar, this.state.toolbar); newState.toolbar = toolbar; } if ( @@ -216,9 +216,14 @@ class WysiwygEditor extends Component { decorators.push( ...getMentionDecorators({ ...this.props.mention, - onChange: this.onChange, + onChange: this.props?.mention?.onChange ? (editorState, selectedSuggestion) => { + this.props.mention.onChange(selectedSuggestion, editorState); + this.onChange(editorState); + } : this.onChange, getEditorState: this.getEditorState, - getSuggestions: this.getSuggestions, + getSuggestions: this.props?.mention?.getSuggestions ? (mentionText) => { + return this.props.mention.getSuggestions(mentionText) + } : this.getSuggestions, getWrapperRef: this.getWrapperRef, modalHandler: this.modalHandler, }) diff --git a/src/decorators/Mention/Suggestion/index.js b/src/decorators/Mention/Suggestion/index.js index f51b92d13..23373849a 100644 --- a/src/decorators/Mention/Suggestion/index.js +++ b/src/decorators/Mention/Suggestion/index.js @@ -34,12 +34,11 @@ class Suggestion { }; } - findSuggestionEntities = (contentBlock, callback) => { + findMentionAttemptText = (contentBlock, callback) => { if (this.config.getEditorState()) { const { separator, trigger, - getSuggestions, getEditorState, } = this.config; const selection = getEditorState().getSelection(); @@ -55,230 +54,217 @@ class Suggestion { : selection.get('focusOffset') + 1 ); let index = text.lastIndexOf(separator + trigger); - let preText = separator + trigger; - if ((index === undefined || index < 0) && text[0] === trigger) { - index = 0; - preText = trigger; - } - if (index >= 0) { - const mentionText = text.substr(index + preText.length, text.length); - const suggestionPresent = getSuggestions().some(suggestion => { - if (suggestion.value) { - if (this.config.caseSensitive) { - return suggestion.value.indexOf(mentionText) >= 0; - } - return ( - suggestion.value - .toLowerCase() - .indexOf(mentionText && mentionText.toLowerCase()) >= 0 - ); - } - return false; - }); - if (suggestionPresent) { - callback(index === 0 ? 0 : index + 1, text.length); - } + if (index >= 0 || text.startsWith(trigger)) { + callback(index === 0 ? 0 : index + 1, text.length); } } } }; - getSuggestionComponent = getSuggestionComponent.bind(this); + getCurrentSuggestionsList = async (mentionText) => { + const suggestionList = await Promise.resolve(this.config.getSuggestions(mentionText)) + return suggestionList + }; getSuggestionDecorator = () => ({ - strategy: this.findSuggestionEntities, - component: this.getSuggestionComponent(), + strategy: this.findMentionAttemptText, + component: SuggestionComponent, + props: { + config: this.config, + getCurrentSuggestionsList: this.getCurrentSuggestionsList + } }); } -function getSuggestionComponent() { - const { config } = this; - return class SuggestionComponent extends Component { - static propTypes = { - children: PropTypes.array, - }; +class SuggestionComponent extends Component { + static propTypes = { + children: PropTypes.array, + }; - state = { - style: { left: 15 }, - activeOption: -1, - showSuggestions: true, - }; + state = { + style: { left: 15 }, + activeOption: -1, + showSuggestions: true, + filteredSuggestions: [] + }; - componentDidMount() { - const editorRect = config.getWrapperRef().getBoundingClientRect(); - const suggestionRect = this.suggestion.getBoundingClientRect(); - const dropdownRect = this.dropdown.getBoundingClientRect(); - let left; - let right; - let bottom; - if ( - editorRect.width < - suggestionRect.left - editorRect.left + dropdownRect.width - ) { - right = 15; - } else { - left = 15; - } - if (editorRect.bottom < dropdownRect.bottom) { - bottom = 0; - } + componentDidMount() { + const editorRect = this.props.config.getWrapperRef().getBoundingClientRect(); + const suggestionRect = this.suggestion.getBoundingClientRect(); + const dropdownRect = this.dropdown.getBoundingClientRect(); + let left; + let right; + let bottom; + if ( + editorRect.width < + suggestionRect.left - editorRect.left + dropdownRect.width + ) { + right = 15; + } else { + left = 15; + } + if (editorRect.bottom < dropdownRect.bottom) { + bottom = 0; + } + KeyDownHandler.registerCallBack(this.onEditorKeyDown); + SuggestionHandler.open(); + this.props.config.modalHandler.setSuggestionCallback(this.closeSuggestionDropdown); + this.filterSuggestions(this.props).then((_filteredSuggestions) => { this.setState({ // eslint-disable-line react/no-did-mount-set-state style: { left, right, bottom }, + // eslint-disable-line react/no-did-mount-set-state + filteredSuggestions: _filteredSuggestions }); - KeyDownHandler.registerCallBack(this.onEditorKeyDown); - SuggestionHandler.open(); - config.modalHandler.setSuggestionCallback(this.closeSuggestionDropdown); - this.filterSuggestions(this.props); - } + }); + } - componentDidUpdate(props) { - const { children } = this.props; - if (children !== props.children) { - this.filterSuggestions(props); + componentDidUpdate(props) { + const { children } = this.props; + if (children !== props.children) { + this.filterSuggestions(props).then((_filteredSuggestions) => { this.setState({ showSuggestions: true, + filteredSuggestions: _filteredSuggestions }); - } + }) } + } - componentWillUnmount() { - KeyDownHandler.deregisterCallBack(this.onEditorKeyDown); + componentWillUnmount() { + KeyDownHandler.deregisterCallBack(this.onEditorKeyDown); + SuggestionHandler.close(); + this.props.config.modalHandler.removeSuggestionCallback(); + } + + onEditorKeyDown = event => { + const { activeOption, filteredSuggestions } = this.state; + const newState = {}; + if (event.key === 'ArrowDown') { + event.preventDefault(); + if (activeOption === filteredSuggestions.length - 1) { + newState.activeOption = 0; + } else { + newState.activeOption = activeOption + 1; + } + } else if (event.key === 'ArrowUp') { + if (activeOption <= 0) { + newState.activeOption = filteredSuggestions.length - 1; + } else { + newState.activeOption = activeOption - 1; + } + } else if (event.key === 'Escape') { + newState.showSuggestions = false; SuggestionHandler.close(); - config.modalHandler.removeSuggestionCallback(); + } else if (event.key === 'Enter') { + this.addMention(); } + this.setState(newState); + }; - onEditorKeyDown = event => { - const { activeOption } = this.state; - const newState = {}; - if (event.key === 'ArrowDown') { - event.preventDefault(); - if (activeOption === this.filteredSuggestions.length - 1) { - newState.activeOption = 0; - } else { - newState.activeOption = activeOption + 1; - } - } else if (event.key === 'ArrowUp') { - if (activeOption <= 0) { - newState.activeOption = this.filteredSuggestions.length - 1; - } else { - newState.activeOption = activeOption - 1; - } - } else if (event.key === 'Escape') { - newState.showSuggestions = false; - SuggestionHandler.close(); - } else if (event.key === 'Enter') { - this.addMention(); - } - this.setState(newState); - }; + onOptionMouseEnter = event => { + const index = event.target.getAttribute('data-index'); + this.setState({ + activeOption: index, + }); + }; - onOptionMouseEnter = event => { - const index = event.target.getAttribute('data-index'); - this.setState({ - activeOption: index, - }); - }; + onOptionMouseLeave = () => { + this.setState({ + activeOption: -1, + }); + }; - onOptionMouseLeave = () => { - this.setState({ - activeOption: -1, - }); - }; + setSuggestionReference = ref => { + this.suggestion = ref; + }; - setSuggestionReference = ref => { - this.suggestion = ref; - }; + setDropdownReference = ref => { + this.dropdown = ref; + }; - setDropdownReference = ref => { - this.dropdown = ref; - }; + closeSuggestionDropdown = () => { + this.setState({ + showSuggestions: false, + }); + }; - closeSuggestionDropdown = () => { - this.setState({ - showSuggestions: false, + filterSuggestions = async (props) => { + const mentionText = props.children[0].props.text.substr(1); + const suggestions = await this.props.getCurrentSuggestionsList(mentionText); + return suggestions && + suggestions.filter(suggestion => { + if (!mentionText || mentionText.length === 0) { + return true; + } + if (this.props.config.caseSensitive) { + return suggestion.value.indexOf(mentionText) >= 0; + } + return ( + suggestion.value + .toLowerCase() + .indexOf(mentionText && mentionText.toLowerCase()) >= 0 + ); }); - }; - - filteredSuggestions = []; - - filterSuggestions = props => { - const mentionText = props.children[0].props.text.substr(1); - const suggestions = config.getSuggestions(); - this.filteredSuggestions = - suggestions && - suggestions.filter(suggestion => { - if (!mentionText || mentionText.length === 0) { - return true; - } - if (config.caseSensitive) { - return suggestion.value.indexOf(mentionText) >= 0; - } - return ( - suggestion.value - .toLowerCase() - .indexOf(mentionText && mentionText.toLowerCase()) >= 0 - ); - }); - }; - - addMention = () => { - const { activeOption } = this.state; - const editorState = config.getEditorState(); - const { onChange, separator, trigger } = config; - const selectedMention = this.filteredSuggestions[activeOption]; - if (selectedMention) { - addMention(editorState, onChange, separator, trigger, selectedMention); - } - }; + }; - render() { - const { children } = this.props; - const { activeOption, showSuggestions } = this.state; - const { dropdownClassName, optionClassName } = config; - return ( - - {children} - {showSuggestions && ( - - {this.filteredSuggestions.map((suggestion, index) => ( - - {suggestion.text} - - ))} - - )} - - ); + addMention = () => { + const { activeOption, filteredSuggestions } = this.state; + const editorState = this.props.config.getEditorState(); + const { onChange, separator, trigger } = this.props.config; + const selectedMention = filteredSuggestions?.[activeOption]; + if (selectedMention) { + addMention(editorState, onChange, separator, trigger, selectedMention); } }; -} + + render() { + const { children } = this.props; + const { activeOption, showSuggestions } = this.state; + const { dropdownClassName, optionClassName } = this.props.config; + return ( + + {children} + {showSuggestions && ( + + {this.state.filteredSuggestions?.map?.((suggestion, index) => ( + + {suggestion.text} + + ))} + + )} + + ); + } +}; + export default Suggestion; diff --git a/src/decorators/Mention/addMention.js b/src/decorators/Mention/addMention.js index d95cbdba7..9ebaeaaa7 100644 --- a/src/decorators/Mention/addMention.js +++ b/src/decorators/Mention/addMention.js @@ -56,5 +56,5 @@ export default function addMention( undefined, ); } - onChange(EditorState.push(newEditorState, contentState, 'insert-characters')); + onChange(EditorState.push(newEditorState, contentState, 'insert-characters'), suggestion); }