diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx
deleted file mode 100644
index 7fc0f79cf4..0000000000
--- a/web/react/components/command_list.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as client from '../utils/client.jsx';
-
-export default class CommandList extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
- this.addFirstCommand = this.addFirstCommand.bind(this);
- this.isEmpty = this.isEmpty.bind(this);
- this.getSuggestedCommands = this.getSuggestedCommands.bind(this);
-
- this.state = {
- suggestions: [],
- cmd: ''
- };
- }
-
- handleClick(i) {
- this.props.addCommand(this.state.suggestions[i].suggestion);
- this.setState({suggestions: [], cmd: ''});
- }
-
- addFirstCommand() {
- if (this.state.suggestions.length === 0) {
- return;
- }
- this.handleClick(0);
- }
-
- isEmpty() {
- return this.state.suggestions.length === 0;
- }
-
- getSuggestedCommands(cmd) {
- if (!cmd || cmd.charAt(0) !== '/') {
- this.setState({suggestions: [], cmd: ''});
- return;
- }
-
- client.executeCommand(
- this.props.channelId,
- cmd,
- true,
- function success(data) {
- if (data.suggestions.length === 1 && data.suggestions[0].suggestion === cmd) {
- data.suggestions = [];
- }
- this.setState({suggestions: data.suggestions, cmd: cmd});
- }.bind(this),
- function fail() {
- }
- );
- }
-
- render() {
- if (this.state.suggestions.length === 0) {
- return (
+ let textbox = null;
+ if (this.props.type === 'input') {
+ textbox = (
-
+ );
+ } else if (this.props.type === 'textarea') {
+ textbox = (
+
+ );
+ }
+
+ const SuggestionListComponent = this.props.listComponent;
+
+ return (
+
+ {textbox}
+
);
}
}
+SuggestionBox.defaultProps = {
+ type: 'input'
+};
+
SuggestionBox.propTypes = {
+ listComponent: React.PropTypes.func.isRequired,
+ type: React.PropTypes.oneOf(['input', 'textarea']).isRequired,
value: React.PropTypes.string.isRequired,
onUserInput: React.PropTypes.func,
providers: React.PropTypes.arrayOf(React.PropTypes.object),
diff --git a/web/react/components/suggestion_list.jsx b/web/react/components/suggestion/suggestion_list.jsx
similarity index 61%
rename from web/react/components/suggestion_list.jsx
rename to web/react/components/suggestion/suggestion_list.jsx
index 04d8f3e608..45843f4c86 100644
--- a/web/react/components/suggestion_list.jsx
+++ b/web/react/components/suggestion/suggestion_list.jsx
@@ -1,15 +1,15 @@
// 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 SuggestionStore from '../stores/suggestion_store.jsx';
-import * as Utils from '../utils/utils.jsx';
+import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import SuggestionStore from '../../stores/suggestion_store.jsx';
export default class SuggestionList extends React.Component {
constructor(props) {
super(props);
+ this.getContent = this.getContent.bind(this);
+
this.handleItemClick = this.handleItemClick.bind(this);
this.handleSuggestionsChanged = this.handleSuggestionsChanged.bind(this);
@@ -27,23 +27,16 @@ export default class SuggestionList extends React.Component {
SuggestionStore.addSuggestionsChangedListener(this.props.suggestionId, this.handleSuggestionsChanged);
}
- componentDidUpdate(prevProps, prevState) {
- if (this.state.items.length > 0 && prevState.items.length === 0) {
- const content = $(ReactDOM.findDOMNode(this.refs.popover)).find('.popover-content');
- content.perfectScrollbar();
- }
- }
-
componentWillUnmount() {
SuggestionStore.removeSuggestionsChangedListener(this.props.suggestionId, this.handleSuggestionsChanged);
}
+ getContent() {
+ return $(ReactDOM.findDOMNode(this.refs.content));
+ }
+
handleItemClick(term, e) {
- AppDispatcher.handleViewAction({
- type: Constants.ActionTypes.SUGGESTION_COMPLETE_WORD,
- id: this.props.suggestionId,
- term
- });
+ EventHelpers.emitCompleteWordSuggestion(this.props.suggestionId, term);
e.preventDefault();
}
@@ -64,7 +57,7 @@ export default class SuggestionList extends React.Component {
}
scrollToItem(term) {
- const content = $(ReactDOM.findDOMNode(this.refs.popover)).find('.popover-content');
+ const content = this.getContent();
const visibleContentHeight = content[0].clientHeight;
const actualContentHeight = content[0].scrollHeight;
@@ -75,7 +68,8 @@ export default class SuggestionList extends React.Component {
const item = $(ReactDOM.findDOMNode(this.refs[term]));
const itemTop = item[0].offsetTop - parseInt(item.css('margin-top'), 10);
- const itemBottom = item[0].offsetTop + item.height() + parseInt(item.css('margin-bottom'), 10);
+ const itemBottomMargin = parseInt(item.css('margin-bottom'), 10) + parseInt(item.css('padding-bottom'), 10);
+ const itemBottom = item[0].offsetTop + item.height() + itemBottomMargin;
if (itemTop - contentTopPadding < contentTop) {
// the item is off the top of the visible space
@@ -87,26 +81,8 @@ export default class SuggestionList extends React.Component {
}
}
- renderChannelDivider(type) {
- let text;
- if (type === Constants.OPEN_CHANNEL) {
- text = 'Public ' + Utils.getChannelTerm(type) + 's';
- } else {
- text = 'Private ' + Utils.getChannelTerm(type) + 's';
- }
-
- return (
-
- {text}
-
- );
- }
-
render() {
- if (this.state.items.length === 0) {
+ if (this.state.items.length === 0 || !this.props.show) {
return null;
}
@@ -119,15 +95,6 @@ export default class SuggestionList extends React.Component {
// ReactComponent names need to be upper case when used in JSX
const Component = this.state.components[i];
- // temporary hack to add dividers between public and private channels in the search suggestion list
- if (i === 0 || item.type !== this.state.items[i - 1].type) {
- if (item.type === Constants.OPEN_CHANNEL) {
- items.push(this.renderChannelDivider(Constants.OPEN_CHANNEL));
- } else if (item.type === Constants.PRIVATE_CHANNEL) {
- items.push(this.renderChannelDivider(Constants.PRIVATE_CHANNEL));
- }
- }
-
items.push(
- {items}
-
+
);
}
}
SuggestionList.propTypes = {
- suggestionId: React.PropTypes.string.isRequired
-};
\ No newline at end of file
+ suggestionId: React.PropTypes.string.isRequired,
+ show: React.PropTypes.bool.isRequired
+};
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 10b3c0069d..107e65f575 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -1,16 +1,15 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import SearchStore from '../stores/search_store.jsx';
-import CommandList from './command_list.jsx';
+import AtMentionProvider from './suggestion/at_mention_provider.jsx';
+import CommandProvider from './suggestion/command_provider.jsx';
+import SuggestionList from './suggestion/suggestion_list.jsx';
+import SuggestionBox from './suggestion/suggestion_box.jsx';
import ErrorStore from '../stores/error_store.jsx';
import * as TextFormatting from '../utils/text_formatting.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
-const KeyCodes = Constants.KeyCodes;
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
export default class Textbox extends React.Component {
@@ -18,32 +17,22 @@ export default class Textbox extends React.Component {
super(props);
this.getStateFromStores = this.getStateFromStores.bind(this);
- this.onListenerChange = this.onListenerChange.bind(this);
this.onRecievedError = this.onRecievedError.bind(this);
- this.updateMentionTab = this.updateMentionTab.bind(this);
- this.handleChange = this.handleChange.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleBackspace = this.handleBackspace.bind(this);
- this.checkForNewMention = this.checkForNewMention.bind(this);
- this.addMention = this.addMention.bind(this);
- this.addCommand = this.addCommand.bind(this);
this.resize = this.resize.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
- this.handlePaste = this.handlePaste.bind(this);
this.showPreview = this.showPreview.bind(this);
this.state = {
- mentionText: '-1',
- mentions: [],
connection: ''
};
- this.caret = -1;
- this.addedMention = false;
- this.doProcessMentions = false;
- this.mentions = [];
+ this.suggestionProviders = [new AtMentionProvider()];
+ if (props.supportsCommands) {
+ this.suggestionProviders.push(new CommandProvider());
+ }
}
getStateFromStores() {
@@ -57,24 +46,15 @@ export default class Textbox extends React.Component {
}
componentDidMount() {
- SearchStore.addAddMentionListener(this.onListenerChange);
ErrorStore.addChangeListener(this.onRecievedError);
this.resize();
- this.updateMentionTab(null);
}
componentWillUnmount() {
- SearchStore.removeAddMentionListener(this.onListenerChange);
ErrorStore.removeChangeListener(this.onRecievedError);
}
- onListenerChange(id, username) {
- if (id === this.props.id) {
- this.addMention(username);
- }
- }
-
onRecievedError() {
const errorState = ErrorStore.getLastError();
@@ -86,158 +66,21 @@ export default class Textbox extends React.Component {
}
componentDidUpdate() {
- if (this.caret >= 0) {
- Utils.setCaretPosition(ReactDOM.findDOMNode(this.refs.message), this.caret);
- this.caret = -1;
- }
- if (this.doProcessMentions) {
- this.updateMentionTab(null);
- this.doProcessMentions = false;
- }
this.resize();
}
- componentWillReceiveProps(nextProps) {
- if (!this.addedMention) {
- this.checkForNewMention(nextProps.messageText);
- }
- const text = ReactDOM.findDOMNode(this.refs.message).value;
- if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) {
- this.doProcessMentions = true;
- }
- this.addedMention = false;
- this.refs.commands.getSuggestedCommands(nextProps.messageText);
- }
-
- updateMentionTab(mentionText) {
- // using setTimeout so dispatch isn't called during an in progress dispatch
- setTimeout(() => {
- AppDispatcher.handleViewAction({
- type: ActionTypes.RECIEVED_MENTION_DATA,
- id: this.props.id,
- mention_text: mentionText
- });
- }, 1);
- }
-
- handleChange() {
- const text = ReactDOM.findDOMNode(this.refs.message).value;
- this.props.onUserInput(text);
- }
-
handleKeyPress(e) {
- const text = ReactDOM.findDOMNode(this.refs.message).value;
-
- if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) {
- this.refs.commands.addFirstCommand();
- e.preventDefault();
- return;
- }
-
- if (!this.doProcessMentions) {
- const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
- const preText = text.substring(0, caret);
- const lastSpace = preText.lastIndexOf(' ');
- const lastAt = preText.lastIndexOf('@');
-
- if (caret > lastAt && lastSpace < lastAt) {
- this.doProcessMentions = true;
- }
- }
-
this.props.onKeyPress(e);
}
handleKeyDown(e) {
- if (Utils.getSelectedText(ReactDOM.findDOMNode(this.refs.message)) !== '') {
- this.doProcessMentions = true;
- }
-
- if (e.keyCode === KeyCodes.BACKSPACE) {
- this.handleBackspace(e);
- } else if (this.props.onKeyDown) {
+ if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
}
- handleBackspace() {
- const text = ReactDOM.findDOMNode(this.refs.message).value;
- if (text.indexOf('/') === 0) {
- this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1));
- }
-
- if (this.doProcessMentions) {
- return;
- }
-
- const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
- const preText = text.substring(0, caret);
- const lastSpace = preText.lastIndexOf(' ');
- const lastAt = preText.lastIndexOf('@');
-
- if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) {
- this.doProcessMentions = true;
- }
- }
-
- checkForNewMention(text) {
- const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
-
- const preText = text.substring(0, caret);
-
- const atIndex = preText.lastIndexOf('@');
-
- // The @ character not typed, so nothing to do.
- if (atIndex === -1) {
- this.updateMentionTab('-1');
- return;
- }
-
- const lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
- const lastSpace = preText.lastIndexOf(' ');
-
- // If there is a space after the last @, nothing to do.
- if (lastSpace > atIndex || lastCharSpace > atIndex) {
- this.updateMentionTab('-1');
- return;
- }
-
- // Get the name typed so far.
- const name = preText.substring(atIndex + 1, preText.length).toLowerCase();
- this.updateMentionTab(name);
- }
-
- addMention(name) {
- const caret = Utils.getCaretPosition(ReactDOM.findDOMNode(this.refs.message));
-
- const text = this.props.messageText;
-
- const preText = text.substring(0, caret);
-
- const atIndex = preText.lastIndexOf('@');
-
- // The @ character not typed, so nothing to do.
- if (atIndex === -1) {
- return;
- }
-
- const prefix = text.substring(0, atIndex);
- const suffix = text.substring(caret, text.length);
- this.caret = prefix.length + name.length + 2;
- this.addedMention = true;
- this.doProcessMentions = true;
-
- this.props.onUserInput(`${prefix}@${name} ${suffix}`);
- }
-
- addCommand(cmd) {
- const elm = ReactDOM.findDOMNode(this.refs.message);
- elm.value = cmd;
- this.handleChange();
- }
-
resize() {
- const e = ReactDOM.findDOMNode(this.refs.message);
+ const e = this.refs.message.getTextbox();
const w = ReactDOM.findDOMNode(this.refs.wrapper);
const prevHeight = $(e).height();
@@ -272,23 +115,19 @@ export default class Textbox extends React.Component {
}
handleFocus() {
- const elm = ReactDOM.findDOMNode(this.refs.message);
+ const elm = this.refs.message.getTextbox();
if (elm.title === elm.value) {
elm.value = '';
}
}
handleBlur() {
- const elm = ReactDOM.findDOMNode(this.refs.message);
+ const elm = this.refs.message.getTextbox();
if (elm.value === '') {
elm.value = elm.title;
}
}
- handlePaste() {
- this.doProcessMentions = true;
- }
-
showPreview(e) {
e.preventDefault();
e.target.blur();
@@ -323,15 +162,11 @@ export default class Textbox extends React.Component {
ref='wrapper'
className='textarea-wrapper'
>
-
-
,
- document.getElementById('post_mention_tab')
- );
-
- ReactDOM.render(
- ,
- document.getElementById('reply_mention_tab')
- );
-
- ReactDOM.render(
- ,
- document.getElementById('edit_mention_tab')
- );
-
//
// Modals
//
diff --git a/web/react/stores/search_store.jsx b/web/react/stores/search_store.jsx
index e8ab6a2ae4..f932c379ac 100644
--- a/web/react/stores/search_store.jsx
+++ b/web/react/stores/search_store.jsx
@@ -12,8 +12,6 @@ var ActionTypes = Constants.ActionTypes;
var CHANGE_EVENT = 'change';
var SEARCH_CHANGE_EVENT = 'search_change';
var SEARCH_TERM_CHANGE_EVENT = 'search_term_change';
-var MENTION_DATA_CHANGE_EVENT = 'mention_data_change';
-var ADD_MENTION_EVENT = 'add_mention';
var SHOW_SEARCH_EVENT = 'show_search';
class SearchStoreClass extends EventEmitter {
@@ -32,10 +30,6 @@ class SearchStoreClass extends EventEmitter {
this.addSearchTermChangeListener = this.addSearchTermChangeListener.bind(this);
this.removeSearchTermChangeListener = this.removeSearchTermChangeListener.bind(this);
- this.emitMentionDataChange = this.emitMentionDataChange.bind(this);
- this.addMentionDataChangeListener = this.addMentionDataChangeListener.bind(this);
- this.removeMentionDataChangeListener = this.removeMentionDataChangeListener.bind(this);
-
this.emitShowSearch = this.emitShowSearch.bind(this);
this.addShowSearchListener = this.addShowSearchListener.bind(this);
this.removeShowSearchListener = this.removeShowSearchListener.bind(this);
@@ -113,30 +107,6 @@ class SearchStoreClass extends EventEmitter {
return BrowserStore.getItem('search_term');
}
- emitMentionDataChange(id, mentionText) {
- this.emit(MENTION_DATA_CHANGE_EVENT, id, mentionText);
- }
-
- addMentionDataChangeListener(callback) {
- this.on(MENTION_DATA_CHANGE_EVENT, callback);
- }
-
- removeMentionDataChangeListener(callback) {
- this.removeListener(MENTION_DATA_CHANGE_EVENT, callback);
- }
-
- emitAddMention(id, username) {
- this.emit(ADD_MENTION_EVENT, id, username);
- }
-
- addAddMentionListener(callback) {
- this.on(ADD_MENTION_EVENT, callback);
- }
-
- removeAddMentionListener(callback) {
- this.removeListener(ADD_MENTION_EVENT, callback);
- }
-
storeSearchResults(results, isMentionSearch) {
BrowserStore.setItem('search_results', results);
BrowserStore.setItem('is_mention_search', Boolean(isMentionSearch));
@@ -157,12 +127,6 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => {
SearchStore.storeSearchTerm(action.term);
SearchStore.emitSearchTermChange(action.do_search, action.is_mention_search);
break;
- case ActionTypes.RECIEVED_MENTION_DATA:
- SearchStore.emitMentionDataChange(action.id, action.mention_text);
- break;
- case ActionTypes.RECIEVED_ADD_MENTION:
- SearchStore.emitAddMention(action.id, action.username);
- break;
case ActionTypes.SHOW_SEARCH:
SearchStore.emitShowSearch();
break;
diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx
index 016929501f..182f5810f5 100644
--- a/web/react/stores/suggestion_store.jsx
+++ b/web/react/stores/suggestion_store.jsx
@@ -38,6 +38,7 @@ class SuggestionStore extends EventEmitter {
// 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();
}
@@ -78,7 +79,8 @@ class SuggestionStore extends EventEmitter {
terms: [],
items: [],
components: [],
- selection: ''
+ selection: '',
+ completeOnSpace: true
});
}
@@ -93,6 +95,12 @@ class SuggestionStore extends EventEmitter {
suggestion.terms = [];
suggestion.items = [];
suggestion.components = [];
+ suggestion.completeOnSpace = true;
+ }
+
+ clearSelection(id) {
+ const suggestion = this.suggestions.get(id);
+
suggestion.selection = '';
}
@@ -112,6 +120,12 @@ class SuggestionStore extends EventEmitter {
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);
@@ -175,6 +189,10 @@ class SuggestionStore extends EventEmitter {
return this.suggestions.get(id).selection;
}
+ shouldCompleteOnSpace(id) {
+ return this.suggestions.get(id).completeOnSpace;
+ }
+
selectNext(id) {
this.setSelectionByDelta(id, 1);
}
@@ -218,11 +236,13 @@ class SuggestionStore extends EventEmitter {
this.emitSuggestionsChanged(id);
break;
case ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS:
- this.setMatchedPretext(id, other.matchedPretext);
- this.addSuggestions(id, other.terms, other.items, other.componentType);
+ 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);
+ this.ensureSelectionExists(id);
+ this.emitSuggestionsChanged(id);
+ }
break;
case ActionTypes.SUGGESTION_SELECT_NEXT:
this.selectNext(id);
@@ -237,10 +257,11 @@ class SuggestionStore extends EventEmitter {
this.setPretext(id, '');
this.clearSuggestions(id);
+ this.clearSelection(id);
this.emitSuggestionsChanged(id);
break;
}
}
}
-export default new SuggestionStore();
\ No newline at end of file
+export default new SuggestionStore();
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 5df43b5483..d97c7c3cb0 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -741,3 +741,27 @@ export function savePreferences(preferences, success, error) {
}
);
}
+
+export function getSuggestedCommands(command, suggestionId, component) {
+ client.executeCommand(
+ '',
+ command,
+ true,
+ (data) => {
+ // pull out the suggested commands from the returned data
+ const terms = data.suggestions.map((suggestion) => suggestion.suggestion);
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
+ id: suggestionId,
+ matchedPretext: command,
+ terms,
+ items: data.suggestions,
+ component
+ });
+ },
+ (err) => {
+ dispatchError(err, 'getCommandSuggestions');
+ }
+ );
+}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index ab09ea919f..788d8a45c1 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -597,7 +597,7 @@ export function applyTheme(theme) {
}
if (theme.centerChannelBg) {
- changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box, .modal .modal-content, .mentions-name, .mentions--top .mentions-box', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .suggestion-content, .modal .modal-content', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
@@ -615,9 +615,9 @@ export function applyTheme(theme) {
changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name, .tip-overlay', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
- changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
- changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
- changeCss('.mentions--top, .command-box', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1);
+ changeCss('.mentions--top, .suggestion-list', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
+ changeCss('.mentions--top, .suggestion-list', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
+ changeCss('.mentions--top, .suggestion-list', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1);
changeCss('.dropdown-menu, .popover ', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 3);
changeCss('.dropdown-menu, .popover ', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 2);
changeCss('.dropdown-menu, .popover ', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 1);
@@ -626,7 +626,7 @@ export function applyTheme(theme) {
changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
- changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .command-box, .modal .modal-content, .settings-modal .settings-table .settings-content .divider-light, .webhooks__container, .dropdown-menu, .modal .modal-header, .popover, .mentions--top .mentions-box', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .suggestion-content, .modal .modal-content, .settings-modal .settings-table .settings-content .divider-light, .webhooks__container, .dropdown-menu, .modal .modal-header, .popover', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.popover.bottom>.arrow', 'border-bottom-color:' + changeOpacity(theme.centerChannelColor, 0.25), 1);
changeCss('.search-help-popover .search-autocomplete__divider span', 'color:' + changeOpacity(theme.centerChannelColor, 0.7), 1);
changeCss('.popover.right>.arrow', 'border-right-color:' + changeOpacity(theme.centerChannelColor, 0.25), 1);
@@ -652,7 +652,7 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('.post:hover, .modal .more-table tbody>tr:hover td, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
- changeCss('.command-name:hover, .mentions-name:hover, .mentions-focus, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover, .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
+ changeCss('.command-name:hover, .mentions-name:hover, .suggestion--selected, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover, .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
changeCss('code', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
changeCss('@media(min-width: 960px){.post.current--user:hover .post__body ', 'background: none;', 1);
changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index f59cefbc6e..ee5b7f6d07 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -7,26 +7,9 @@
@include border-radius(3px);
}
-.mentions--top {
- position: absolute;
- z-index: 1060;
- @extend %popover-box-shadow;
- .mentions-box {
- width: 100%;
- height: 100%;
- position: absolute;
- background-color: #fff;
- border: $border-gray;
- overflow-x: hidden;
- overflow-y: scroll;
- bottom: 0;
- }
-}
-
.mentions-name {
position:relative;
width:100%;
- background-color:#fff;
height:36px;
padding:2px;
z-index:101;
@@ -57,4 +40,4 @@
.mention-highlight {
background-color:#fff2bb;
-}
\ No newline at end of file
+}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 4a56bc6c75..01c9e136cb 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -405,3 +405,11 @@
@include opacity(0.7);
}
}
+
+.modal-body.edit-modal-body {
+ overflow: visible;
+
+ .suggestion-content {
+ max-height: 150px;
+ }
+}
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index 43162831d7..735b2a99e5 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -93,6 +93,10 @@
padding-bottom: 10px;
}
}
+
+ .suggestion-content {
+ max-height: 120px;
+ }
}
.sidebar-right-container {
diff --git a/web/sass-files/sass/partials/_command-box.scss b/web/sass-files/sass/partials/_suggestion_list.scss
similarity index 57%
rename from web/sass-files/sass/partials/_command-box.scss
rename to web/sass-files/sass/partials/_suggestion_list.scss
index 184fb55ebd..c3df889645 100644
--- a/web/sass-files/sass/partials/_command-box.scss
+++ b/web/sass-files/sass/partials/_suggestion_list.scss
@@ -1,15 +1,29 @@
-.command-box {
+.suggestion-list {
+ width: 100%;
+ z-index: 100;
+ @extend %popover-box-shadow;
+}
+
+.suggestion-list--top {
position: absolute;
- background-color: #fff;
+}
+
+.suggestion-content {
width: 100%;
+ max-height: 292px;
+ background-color: #fff;
border: $border-gray;
- bottom: 38px;
- overflow: auto;
- z-index: 100;
- @extend %popover-box-shadow;
- .sidebar--right & {
- bottom: 100px;
- }
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+.suggestion-content--top {
+ position: absolute;
+ bottom: 0;
+}
+
+.suggestion--selected {
+ // set by theme code
}
.command-name {
@@ -20,9 +34,7 @@
z-index: 101;
font-size: 0.95em;
border-bottom: 1px solid #ddd;
- &:hover {
- background-color: #e8eaed;
- }
+
.command__desc {
margin-left: 5px;
@include opacity(0.5);
@@ -32,4 +44,4 @@
.command-desc {
color: #a7a8ab;
-}
\ No newline at end of file
+}
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index 01f654eec1..e7fd7c9768 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -32,7 +32,6 @@
@import "partials/modal";
@import "partials/forms";
@import "partials/mentions";
-@import "partials/command-box";
@import "partials/error";
@import "partials/error-bar";
@import "partials/loading";
@@ -40,6 +39,7 @@
@import "partials/markdown";
@import "partials/tutorial";
@import "partials/statistics";
+@import "partials/suggestion_list";
// Elements
@import "partials/tooltips";
diff --git a/web/templates/channel.html b/web/templates/channel.html
index 7b8f6a243c..8abbe36df3 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -26,13 +26,13 @@