summaryrefslogtreecommitdiffstats
path: root/webapp/components/suggestion/suggestion_box.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/suggestion/suggestion_box.jsx')
-rw-r--r--webapp/components/suggestion/suggestion_box.jsx441
1 files changed, 0 insertions, 441 deletions
diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx
deleted file mode 100644
index 8f50a23ef..000000000
--- a/webapp/components/suggestion/suggestion_box.jsx
+++ /dev/null
@@ -1,441 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import SuggestionStore from 'stores/suggestion_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import AutosizeTextarea from 'components/autosize_textarea.jsx';
-
-const KeyCodes = Constants.KeyCodes;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SuggestionBox extends React.Component {
- static propTypes = {
-
- /**
- * The list component to render, usually SuggestionList
- */
- listComponent: PropTypes.func.isRequired,
-
- /**
- * The HTML input box type
- */
- type: PropTypes.oneOf(['input', 'textarea', 'search']).isRequired,
-
- /**
- * The value of in the input
- */
- value: PropTypes.string.isRequired,
-
- /**
- * Array of suggestion providers
- */
- providers: PropTypes.arrayOf(PropTypes.object),
-
- /**
- * Where the list will be displayed relative to the input box, defaults to 'top'
- */
- listStyle: PropTypes.string,
-
- /**
- * Set to true to draw dividers between types of list items, defaults to false
- */
- renderDividers: PropTypes.bool,
-
- /**
- * Set to allow TAB to select an item in the list, defaults to true
- */
- completeOnTab: PropTypes.bool,
-
- /**
- * Function called when input box gains focus
- */
- onFocus: PropTypes.func,
-
- /**
- * Function called when input box loses focus
- */
- onBlur: PropTypes.func,
-
- /**
- * Function called when input box value changes
- */
- onChange: PropTypes.func,
-
- /**
- * Function called when a key is pressed and the input box is in focus
- */
- onKeyDown: PropTypes.func,
-
- /**
- * Function called when an item is selected
- */
- onItemSelected: PropTypes.func,
-
- /**
- * Flags if the suggestion_box is for the RHS (Reply).
- */
- isRHS: PropTypes.bool,
-
- /**
- * Function called when @mention is clicked
- */
- popoverMentionKeyClick: PropTypes.bool,
-
- /**
- * The number of characters required to show the suggestion list, defaults to 1
- */
- requiredCharacters: PropTypes.number,
-
- /**
- * If true, the suggestion box is opened on focus, default to false
- */
- openOnFocus: PropTypes.bool
- }
-
- static defaultProps = {
- type: 'input',
- listStyle: 'top',
- renderDividers: false,
- completeOnTab: true,
- isRHS: false,
- requiredCharacters: 1,
- openOnFocus: false
- }
-
- constructor(props) {
- super(props);
-
- this.handleBlur = this.handleBlur.bind(this);
- this.handleFocus = this.handleFocus.bind(this);
-
- this.handlePopoverMentionKeyClick = this.handlePopoverMentionKeyClick.bind(this);
- this.handleCompleteWord = this.handleCompleteWord.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.handleCompositionStart = this.handleCompositionStart.bind(this);
- this.handleCompositionUpdate = this.handleCompositionUpdate.bind(this);
- this.handleCompositionEnd = this.handleCompositionEnd.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handlePretextChanged = this.handlePretextChanged.bind(this);
- this.blur = this.blur.bind(this);
-
- this.suggestionId = Utils.generateId();
- SuggestionStore.registerSuggestionBox(this.suggestionId);
-
- // Keep track of whether we're composing a CJK character so we can make suggestions for partial characters
- this.composing = false;
- }
-
- componentDidMount() {
- if (this.props.popoverMentionKeyClick) {
- SuggestionStore.addPopoverMentionKeyClickListener(this.props.isRHS, this.handlePopoverMentionKeyClick);
- }
- SuggestionStore.addPretextChangedListener(this.suggestionId, this.handlePretextChanged);
- }
-
- componentWillUnmount() {
- if (this.props.popoverMentionKeyClick) {
- SuggestionStore.removePopoverMentionKeyClickListener(this.props.isRHS, this.handlePopoverMentionKeyClick);
- }
- SuggestionStore.removePretextChangedListener(this.suggestionId, this.handlePretextChanged);
-
- SuggestionStore.unregisterSuggestionBox(this.suggestionId);
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.providers !== prevProps.providers) {
- const textbox = this.getTextbox();
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
- }
-
- getTextbox() {
- if (this.props.type === 'textarea' && this.refs.textbox) {
- const node = this.refs.textbox.getDOMNode();
- return node;
- }
-
- return this.refs.textbox;
- }
-
- recalculateSize() {
- if (this.props.type === 'textarea') {
- this.refs.textbox.recalculateSize();
- }
- }
-
- handleBlur() {
- setTimeout(() => {
- // Delay this slightly so that we don't clear the suggestions before we run click handlers on SuggestionList
- GlobalActions.emitClearSuggestions(this.suggestionId);
- }, 200);
-
- if (this.props.onBlur) {
- this.props.onBlur();
- }
- }
-
- handleFocus() {
- if (this.props.openOnFocus) {
- setTimeout(() => {
- const textbox = this.getTextbox();
- if (textbox) {
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
- if (pretext.length >= this.props.requiredCharacters) {
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
- }
- });
- }
-
- if (this.props.onFocus) {
- this.props.onFocus();
- }
- }
-
- handleChange(e) {
- const textbox = this.getTextbox();
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
-
- if (!this.composing && SuggestionStore.getPretext(this.suggestionId) !== pretext && pretext.length >= this.props.requiredCharacters) {
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
-
- if (this.props.onChange) {
- this.props.onChange(e);
- }
- }
-
- handleCompositionStart() {
- this.composing = true;
- }
-
- handleCompositionUpdate(e) {
- if (!e.data) {
- return;
- }
-
- // The caret appears before the CJK character currently being composed, so re-add it to the pretext
- const textbox = this.getTextbox();
- const pretext = textbox.value.substring(0, textbox.selectionStart) + e.data;
-
- if (SuggestionStore.getPretext(this.suggestionId) !== pretext) {
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
- }
-
- handleCompositionEnd() {
- this.composing = false;
- }
-
- handlePopoverMentionKeyClick(mentionKey) {
- let insertText = '@' + mentionKey;
-
- // if the current text does not end with a whitespace, then insert a space
- if (this.refs.textbox.value && (/[^\s]$/).test(this.refs.textbox.value)) {
- insertText = ' ' + insertText;
- }
-
- this.handleCompleteWord(insertText, '', false);
- }
-
- handleCompleteWord(term, matchedPretext, shouldEmitWordSuggestion = true) {
- const textbox = this.getTextbox();
- const caret = textbox.selectionEnd;
- const text = this.props.value;
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
-
- let prefix;
- if (pretext.endsWith(matchedPretext)) {
- prefix = pretext.substring(0, pretext.length - matchedPretext.length);
- } else {
- // the pretext has changed since we got a term to complete so see if the term still fits the pretext
- const termWithoutMatched = term.substring(matchedPretext.length);
- const overlap = SuggestionBox.findOverlap(pretext, termWithoutMatched);
-
- prefix = pretext.substring(0, pretext.length - overlap.length - matchedPretext.length);
- }
-
- const suffix = text.substring(caret);
-
- const newValue = prefix + term + ' ' + suffix;
- this.refs.textbox.value = newValue;
-
- if (this.props.onChange) {
- // fake an input event to send back to parent components
- const e = {
- target: this.refs.textbox
- };
-
- // don't call handleChange or we'll get into an event loop
- this.props.onChange(e);
- }
-
- if (this.props.onItemSelected) {
- const items = SuggestionStore.getItems(this.suggestionId);
- const terms = SuggestionStore.getTerms(this.suggestionId);
- for (let i = 0; i < terms.length; i++) {
- if (terms[i] === term) {
- this.props.onItemSelected(items[i]);
- break;
- }
- }
- }
-
- textbox.focus();
-
- // set the caret position after the next rendering
- window.requestAnimationFrame(() => {
- if (textbox.value === newValue) {
- Utils.setCaretPosition(textbox, prefix.length + term.length + 1);
- }
- });
-
- for (const provider of this.props.providers) {
- if (provider.handleCompleteWord) {
- provider.handleCompleteWord(term, matchedPretext);
- }
- }
- if (shouldEmitWordSuggestion) {
- GlobalActions.emitCompleteWordSuggestion(this.suggestionId);
- }
- }
-
- handleKeyDown(e) {
- if (this.props.value && SuggestionStore.hasSuggestions(this.suggestionId)) {
- if (e.which === KeyCodes.UP) {
- GlobalActions.emitSelectPreviousSuggestion(this.suggestionId);
- e.preventDefault();
- } else if (e.which === KeyCodes.DOWN) {
- GlobalActions.emitSelectNextSuggestion(this.suggestionId);
- e.preventDefault();
- } else if (e.which === KeyCodes.ENTER || (this.props.completeOnTab && e.which === KeyCodes.TAB)) {
- this.handleCompleteWord(SuggestionStore.getSelection(this.suggestionId), SuggestionStore.getSelectedMatchedPretext(this.suggestionId));
- if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- e.preventDefault();
- } else if (e.which === KeyCodes.ESCAPE) {
- GlobalActions.emitClearSuggestions(this.suggestionId);
- e.preventDefault();
- e.stopPropagation();
- } else if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- } else if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- }
-
- handlePretextChanged(pretext) {
- let handled = false;
- for (const provider of this.props.providers) {
- handled = provider.handlePretextChanged(this.suggestionId, pretext) || handled;
- }
-
- if (!handled) {
- SuggestionStore.clearSuggestions(this.suggestionId);
- }
- }
-
- blur() {
- this.refs.textbox.blur();
- }
-
- render() {
- const {
- type,
- listComponent,
- listStyle,
- renderDividers,
- ...props
- } = this.props;
-
- // Don't pass props used by SuggestionBox
- Reflect.deleteProperty(props, 'providers');
- Reflect.deleteProperty(props, 'onChange'); // We use onInput instead of onChange on the actual input
- Reflect.deleteProperty(props, 'onItemSelected');
- Reflect.deleteProperty(props, 'completeOnTab');
- Reflect.deleteProperty(props, 'isRHS');
- Reflect.deleteProperty(props, 'popoverMentionKeyClick');
- Reflect.deleteProperty(props, 'requiredCharacters');
- Reflect.deleteProperty(props, 'openOnFocus');
-
- const childProps = {
- ref: 'textbox',
- onBlur: this.handleBlur,
- onFocus: this.handleFocus,
- onInput: this.handleChange,
- onChange() { /* this is only here to suppress warnings about onChange not being implemented for read-write inputs */ },
- onCompositionStart: this.handleCompositionStart,
- onCompositionUpdate: this.handleCompositionUpdate,
- onCompositionEnd: this.handleCompositionEnd,
- onKeyDown: this.handleKeyDown
- };
-
- let textbox = null;
- if (type === 'input') {
- textbox = (
- <input
- type='text'
- autoComplete='off'
- {...props}
- {...childProps}
- />
- );
- } else if (type === 'search') {
- textbox = (
- <input
- type='search'
- autoComplete='off'
- {...props}
- {...childProps}
- />
- );
- } else if (type === 'textarea') {
- textbox = (
- <AutosizeTextarea
- {...props}
- {...childProps}
- />
- );
- }
-
- // This needs to be upper case so React doesn't think it's an html tag
- const SuggestionListComponent = listComponent;
-
- return (
- <div ref='container'>
- {textbox}
- {this.props.value.length >= this.props.requiredCharacters &&
- <SuggestionListComponent
- suggestionId={this.suggestionId}
- location={listStyle}
- renderDividers={renderDividers}
- onCompleteWord={this.handleCompleteWord}
- />
- }
- </div>
- );
- }
-
- // Finds the longest substring that's at both the end of b and the start of a. For example,
- // if a = "firepit" and b = "pitbull", findOverlap would return "pit".
- static findOverlap(a, b) {
- for (let i = b.length; i > 0; i--) {
- const substring = b.substring(0, i);
-
- if (a.endsWith(substring)) {
- return substring;
- }
- }
-
- return '';
- }
-}