<commit_before>// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

const ChannelStore = require('../stores/channel_store.jsx');
const KeyCodes = require('../utils/constants.jsx').KeyCodes;
const Popover = ReactBootstrap.Popover;
const UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');

const patterns = new Map([
    ['channels', /\b(?:in|channel):\s*(\S*)$/i],
    ['users', /\bfrom:\s*(\S*)$/i]
]);

export default class SearchAutocomplete extends React.Component {
    constructor(props) {
        super(props);

        this.handleClick = this.handleClick.bind(this);
        this.handleDocumentClick = this.handleDocumentClick.bind(this);
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleKeyDown = this.handleKeyDown.bind(this);

        this.completeWord = this.completeWord.bind(this);
        this.getSelection = this.getSelection.bind(this);
        this.scrollToItem = this.scrollToItem.bind(this);
        this.updateSuggestions = this.updateSuggestions.bind(this);

        this.renderChannelSuggestion = this.renderChannelSuggestion.bind(this);
        this.renderUserSuggestion = this.renderUserSuggestion.bind(this);

        this.state = {
            show: false,
            mode: '',
            filter: '',
            selection: 0,
            suggestions: new Map()
        };
    }

    componentDidMount() {
        $(document).on('click', this.handleDocumentClick);
    }

    componentDidUpdate(prevProps, prevState) {
        const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');

        if (this.state.show) {
            if (!prevState.show) {
                content.perfectScrollbar();
                content.css('max-height', $(window).height() - 200);
            }

            // keep the keyboard selection visible when scrolling
            this.scrollToItem(this.getSelection());
        }
    }

    componentWillUnmount() {
        $(document).off('click', this.handleDocumentClick);
    }

    handleClick(value) {
        this.completeWord(value);
    }

    handleDocumentClick(e) {
        const container = $(ReactDOM.findDOMNode(this.refs.searchPopover));

        if (!(container.is(e.target) || container.has(e.target).length > 0)) {
            this.setState({
                show: false
            });
        }
    }

    handleInputChange(textbox, text) {
        const caret = Utils.getCaretPosition(textbox);
        const preText = text.substring(0, caret);

        let mode = '';
        let filter = '';
        for (const [modeForPattern, pattern] of patterns) {
            const result = pattern.exec(preText);

            if (result) {
                mode = modeForPattern;
                filter = result[1];
                break;
            }
        }

        if (mode !== this.state.mode || filter !== this.state.filter) {
            this.updateSuggestions(mode, filter);
        }

        this.setState({
            mode,
            filter,
            show: mode || filter
        });
    }

    handleKeyDown(e) {
        if (!this.state.show || this.state.suggestions.length === 0) {
            return;
        }

        if (e.which === KeyCodes.UP || e.which === KeyCodes.DOWN) {
            e.preventDefault();

            let selection = this.state.selection;

            if (e.which === KeyCodes.UP) {
                selection -= 1;
            } else {
                selection += 1;
            }

            if (selection >= 0 && selection < this.state.suggestions.length) {
                this.setState({
                    selection
                });
            }
        } else if (e.which === KeyCodes.ENTER || e.which === KeyCodes.SPACE) {
            e.preventDefault();

            this.completeWord(this.getSelection());
        }
    }

    completeWord(value) {
        // add a space so that anything else typed doesn't interfere with the search flag
        this.props.completeWord(this.state.filter, value + ' ');

        this.setState({
            show: false,
            mode: '',
            filter: '',
            selection: 0
        });
    }

    getSelection() {
        if (this.state.mode === 'channels') {
            return this.state.suggestions[this.state.selection].name;
        } else if (this.state.mode === 'users') {
            return this.state.suggestions[this.state.selection].username;
        }

        return '';
    }

    scrollToItem(itemName) {
        const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');
        const visibleContentHeight = content[0].clientHeight;
        const actualContentHeight = content[0].scrollHeight;

        if (this.state.suggestions.length > 0 && visibleContentHeight < actualContentHeight) {<commit_msg>Fixed some console errors with the search autocomplete when there are no suggestions to show<commit_after>// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

const ChannelStore = require('../stores/channel_store.jsx');
const KeyCodes = require('../utils/constants.jsx').KeyCodes;
const Popover = ReactBootstrap.Popover;
const UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');

const patterns = new Map([
    ['channels', /\b(?:in|channel):\s*(\S*)$/i],
    ['users', /\bfrom:\s*(\S*)$/i]
]);

export default class SearchAutocomplete extends React.Component {
    constructor(props) {
        super(props);

        this.handleClick = this.handleClick.bind(this);
        this.handleDocumentClick = this.handleDocumentClick.bind(this);
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleKeyDown = this.handleKeyDown.bind(this);

        this.completeWord = this.completeWord.bind(this);
        this.getSelection = this.getSelection.bind(this);
        this.scrollToItem = this.scrollToItem.bind(this);
        this.updateSuggestions = this.updateSuggestions.bind(this);

        this.renderChannelSuggestion = this.renderChannelSuggestion.bind(this);
        this.renderUserSuggestion = this.renderUserSuggestion.bind(this);

        this.state = {
            show: false,
            mode: '',
            filter: '',
            selection: 0,
            suggestions: new Map()
        };
    }

    componentDidMount() {
        $(document).on('click', this.handleDocumentClick);
    }

    componentDidUpdate(prevProps, prevState) {
        const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');

        if (this.state.show && this.state.suggestions.length > 0) {
            if (!prevState.show) {
                content.perfectScrollbar();
                content.css('max-height', $(window).height() - 200);
            }

            // keep the keyboard selection visible when scrolling
            this.scrollToItem(this.getSelection());
        }
    }

    componentWillUnmount() {
        $(document).off('click', this.handleDocumentClick);
    }

    handleClick(value) {
        this.completeWord(value);
    }

    handleDocumentClick(e) {
        const container = $(ReactDOM.findDOMNode(this.refs.searchPopover));

        if (!(container.is(e.target) || container.has(e.target).length > 0)) {
            this.setState({
                show: false
            });
        }
    }

    handleInputChange(textbox, text) {
        const caret = Utils.getCaretPosition(textbox);
        const preText = text.substring(0, caret);

        let mode = '';
        let filter = '';
        for (const [modeForPattern, pattern] of patterns) {
            const result = pattern.exec(preText);

            if (result) {
                mode = modeForPattern;
                filter = result[1];
                break;
            }
        }

        if (mode !== this.state.mode || filter !== this.state.filter) {
            this.updateSuggestions(mode, filter);
        }

        this.setState({
            mode,
            filter,
            show: mode || filter
        });
    }

    handleKeyDown(e) {
        if (!this.state.show || this.state.suggestions.length === 0) {
            return;
        }

        if (e.which === KeyCodes.UP || e.which === KeyCodes.DOWN) {
            e.preventDefault();

            let selection = this.state.selection;

            if (e.which === KeyCodes.UP) {
                selection -= 1;
            } else {
                selection += 1;
            }

            if (selection >= 0 && selection < this.state.suggestions.length) {
                this.setState({
                    selection
                });
            }
        } else if (e.which === KeyCodes.ENTER || e.which === KeyCodes.SPACE) {
            e.preventDefault();

            this.completeWord(this.getSelection());
        }
    }

    completeWord(value) {
        // add a space so that anything else typed doesn't interfere with the search flag
        this.props.completeWord(this.state.filter, value + ' ');

        this.setState({
            show: false,
            mode: '',
            filter: '',
            selection: 0
        });
    }

    getSelection() {
        if (this.state.suggestions.length > 0) {
            if (this.state.mode === 'channels') {
                return this.state.suggestions[this.state.selection].name;
            } else if (this.state.mode === 'users') {
                return this.state.suggestions[this.state.selection].username;
            }
        }

        return '';
    }

    scrollToItem(itemName) {
        const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');
        const visibleContentHeight = content[0].clientHeight;
        const actualContentHeight = content[0].scrollHeight;

        if (this.state.suggestions.length > 0 && visibleContentHeight < actualContentHeight) {
{'fraction_non_alphanumeric': 0.13231652966227592, 'fraction_numerical': 0.003961572744379519, 'mean_word_length': 1.986690328305235, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7548673', 'n_tokens_mistral': 2849, 'n_tokens_neox': 2721, 'n_words': 519}