summaryrefslogtreecommitdiffstats
path: root/web/react/stores/suggestion_store.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/stores/suggestion_store.jsx')
-rw-r--r--web/react/stores/suggestion_store.jsx272
1 files changed, 272 insertions, 0 deletions
diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx
new file mode 100644
index 000000000..2250ec234
--- /dev/null
+++ b/web/react/stores/suggestion_store.jsx
@@ -0,0 +1,272 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
+import Constants from '../utils/constants.jsx';
+import EventEmitter from 'events';
+
+const ActionTypes = Constants.ActionTypes;
+
+const COMPLETE_WORD_EVENT = 'complete_word';
+const PRETEXT_CHANGED_EVENT = 'pretext_changed';
+const SUGGESTIONS_CHANGED_EVENT = 'suggestions_changed';
+
+class SuggestionStore extends EventEmitter {
+ constructor() {
+ super();
+
+ this.addSuggestionsChangedListener = this.addSuggestionsChangedListener.bind(this);
+ this.removeSuggestionsChangedListener = this.removeSuggestionsChangedListener.bind(this);
+ this.emitSuggestionsChanged = this.emitSuggestionsChanged.bind(this);
+
+ this.addPretextChangedListener = this.addPretextChangedListener.bind(this);
+ this.removePretextChangedListener = this.removePretextChangedListener.bind(this);
+ this.emitPretextChanged = this.emitPretextChanged.bind(this);
+
+ this.addCompleteWordListener = this.addCompleteWordListener.bind(this);
+ this.removeCompleteWordListener = this.removeCompleteWordListener.bind(this);
+ this.emitCompleteWord = this.emitCompleteWord.bind(this);
+
+ this.handleEventPayload = this.handleEventPayload.bind(this);
+ this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
+
+ // this.suggestions stores the state of all SuggestionBoxes by mapping their unique identifier to an
+ // object with the following fields:
+ // pretext: the text before the cursor
+ // matchedPretext: the text before the cursor that will be replaced if an autocomplete term is selected
+ // terms: a list of strings which the previously typed text may be replaced by
+ // items: a list of objects backing the terms which may be used in rendering
+ // components: a list of react components that can be used to render their corresponding item
+ // selection: the term currently selected by the keyboard
+ // completeOnSpace: whether or not space will trigger the term to be autocompleted
+ this.suggestions = new Map();
+ }
+
+ addSuggestionsChangedListener(id, callback) {
+ this.on(SUGGESTIONS_CHANGED_EVENT + id, callback);
+ }
+ removeSuggestionsChangedListener(id, callback) {
+ this.removeListener(SUGGESTIONS_CHANGED_EVENT + id, callback);
+ }
+ emitSuggestionsChanged(id) {
+ this.emit(SUGGESTIONS_CHANGED_EVENT + id);
+ }
+
+ addPretextChangedListener(id, callback) {
+ this.on(PRETEXT_CHANGED_EVENT + id, callback);
+ }
+ removePretextChangedListener(id, callback) {
+ this.removeListener(PRETEXT_CHANGED_EVENT + id, callback);
+ }
+ emitPretextChanged(id, pretext) {
+ this.emit(PRETEXT_CHANGED_EVENT + id, pretext);
+ }
+
+ addCompleteWordListener(id, callback) {
+ this.on(COMPLETE_WORD_EVENT + id, callback);
+ }
+ removeCompleteWordListener(id, callback) {
+ this.removeListener(COMPLETE_WORD_EVENT + id, callback);
+ }
+ emitCompleteWord(id, term) {
+ this.emit(COMPLETE_WORD_EVENT + id, term);
+ }
+
+ registerSuggestionBox(id) {
+ this.suggestions.set(id, {
+ pretext: '',
+ matchedPretext: '',
+ terms: [],
+ items: [],
+ components: [],
+ selection: '',
+ completeOnSpace: true
+ });
+ }
+
+ unregisterSuggestionBox(id) {
+ this.suggestions.delete(id);
+ }
+
+ clearSuggestions(id) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.matchedPretext = '';
+ suggestion.terms = [];
+ suggestion.items = [];
+ suggestion.components = [];
+ suggestion.completeOnSpace = true;
+ }
+
+ clearSelection(id) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.selection = '';
+ }
+
+ hasSuggestions(id) {
+ return this.suggestions.get(id).terms.length > 0;
+ }
+
+ setPretext(id, pretext) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.pretext = pretext;
+ }
+
+ setMatchedPretext(id, matchedPretext) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.matchedPretext = matchedPretext;
+ }
+
+ setCompleteOnSpace(id, completeOnSpace) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.completeOnSpace = completeOnSpace;
+ }
+
+ addSuggestion(id, term, item, component) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.terms.push(term);
+ suggestion.items.push(item);
+ suggestion.components.push(component);
+ }
+
+ addSuggestions(id, terms, items, component) {
+ const suggestion = this.suggestions.get(id);
+
+ suggestion.terms.push(...terms);
+ suggestion.items.push(...items);
+
+ for (let i = 0; i < terms.length; i++) {
+ suggestion.components.push(component);
+ }
+ }
+
+ // make sure that if suggestions exist, then one of them is selected. return true if the selection changes.
+ ensureSelectionExists(id) {
+ const suggestion = this.suggestions.get(id);
+
+ if (suggestion.terms.length > 0) {
+ // if the current selection is no longer in the map, select the first term in the list
+ if (!suggestion.selection || suggestion.terms.indexOf(suggestion.selection) === -1) {
+ suggestion.selection = suggestion.terms[0];
+
+ return true;
+ }
+ } else if (suggestion.selection) {
+ suggestion.selection = '';
+
+ return true;
+ }
+
+ return false;
+ }
+
+ getPretext(id) {
+ return this.suggestions.get(id).pretext;
+ }
+
+ getMatchedPretext(id) {
+ return this.suggestions.get(id).matchedPretext;
+ }
+
+ getItems(id) {
+ return this.suggestions.get(id).items;
+ }
+
+ getTerms(id) {
+ return this.suggestions.get(id).terms;
+ }
+
+ getComponents(id) {
+ return this.suggestions.get(id).components;
+ }
+
+ getSelection(id) {
+ return this.suggestions.get(id).selection;
+ }
+
+ shouldCompleteOnSpace(id) {
+ return this.suggestions.get(id).completeOnSpace;
+ }
+
+ selectNext(id) {
+ this.setSelectionByDelta(id, 1);
+ }
+
+ selectPrevious(id) {
+ this.setSelectionByDelta(id, -1);
+ }
+
+ setSelectionByDelta(id, delta) {
+ const suggestion = this.suggestions.get(id);
+
+ let selectionIndex = suggestion.terms.indexOf(suggestion.selection);
+
+ if (selectionIndex === -1) {
+ // this should never happen since selection should always be in terms
+ throw new Error('selection is not in terms');
+ }
+
+ selectionIndex += delta;
+
+ if (selectionIndex < 0) {
+ selectionIndex = 0;
+ } else if (selectionIndex > suggestion.terms.length - 1) {
+ selectionIndex = suggestion.terms.length - 1;
+ }
+
+ suggestion.selection = suggestion.terms[selectionIndex];
+ }
+
+ handleEventPayload(payload) {
+ const {type, id, ...other} = payload.action; // eslint-disable-line no-redeclare
+
+ switch (type) {
+ case ActionTypes.SUGGESTION_PRETEXT_CHANGED:
+ this.clearSuggestions(id);
+
+ this.setPretext(id, other.pretext);
+ this.emitPretextChanged(id, other.pretext);
+
+ this.ensureSelectionExists(id);
+ this.emitSuggestionsChanged(id);
+ break;
+ case ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS:
+ if (other.matchedPretext === this.getMatchedPretext(id)) {
+ // ensure the matched pretext hasn't changed so that we don't receive suggestions for outdated pretext
+ this.addSuggestions(id, other.terms, other.items, other.component);
+
+ this.ensureSelectionExists(id);
+ this.emitSuggestionsChanged(id);
+ }
+ break;
+ case ActionTypes.SUGGESTION_CLEAR_SUGGESTIONS:
+ this.clearSuggestions(id);
+ this.clearSelection(id);
+ this.emitSuggestionsChanged(id);
+ break;
+ case ActionTypes.SUGGESTION_SELECT_NEXT:
+ this.selectNext(id);
+ this.emitSuggestionsChanged(id);
+ break;
+ case ActionTypes.SUGGESTION_SELECT_PREVIOUS:
+ this.selectPrevious(id);
+ this.emitSuggestionsChanged(id);
+ break;
+ case ActionTypes.SUGGESTION_COMPLETE_WORD:
+ this.emitCompleteWord(id, other.term || this.getSelection(id), this.getMatchedPretext(id));
+
+ this.setPretext(id, '');
+ this.clearSuggestions(id);
+ this.clearSelection(id);
+ this.emitSuggestionsChanged(id);
+ break;
+ }
+ }
+}
+
+export default new SuggestionStore();