summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx25
-rw-r--r--web/react/components/admin_console/select_team_modal.jsx2
-rw-r--r--web/react/components/post.jsx4
-rw-r--r--web/react/components/suggestion/emoticon_provider.jsx74
-rw-r--r--web/react/components/suggestion/search_suggestion_list.jsx2
-rw-r--r--web/react/components/suggestion/suggestion_box.jsx32
-rw-r--r--web/react/components/suggestion/suggestion_list.jsx6
-rw-r--r--web/react/components/textbox.jsx3
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx9
-rw-r--r--web/react/dispatcher/event_helpers.jsx7
-rw-r--r--web/react/stores/suggestion_store.jsx5
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/emoticons.jsx8
-rw-r--r--web/react/utils/utils.jsx22
14 files changed, 143 insertions, 57 deletions
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 076a07618..cc98c495e 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -5,6 +5,9 @@ import AdminSidebarHeader from './admin_sidebar_header.jsx';
import SelectTeamModal from './select_team_modal.jsx';
import * as Utils from '../../utils/utils.jsx';
+const Tooltip = ReactBootstrap.Tooltip;
+const OverlayTrigger = ReactBootstrap.OverlayTrigger;
+
export default class AdminSidebar extends React.Component {
constructor(props) {
super(props);
@@ -80,6 +83,12 @@ export default class AdminSidebar extends React.Component {
render() {
var count = '*';
var teams = 'Loading';
+ const removeTooltip = (
+ <Tooltip id='remove-team-tooltip'>{'Remove team from sidebar menu'}</Tooltip>
+ );
+ const addTeamTooltip = (
+ <Tooltip id='add-team-tooltip'>{'Add team from sidebar menu'}</Tooltip>
+ );
if (this.props.teams != null) {
count = '' + Object.keys(this.props.teams).length;
@@ -102,14 +111,19 @@ export default class AdminSidebar extends React.Component {
className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id)}
>
{team.name}
+ <OverlayTrigger
+ delayShow={1000}
+ placement='top'
+ overlay={removeTooltip}
+ >
<span
className='menu-icon--right menu__close'
onClick={this.removeTeam.bind(this, team.id)}
style={{cursor: 'pointer'}}
- title='Remove team from sidebar menu'
>
- {'x'}
+ {'×'}
</span>
+ </OverlayTrigger>
</a>
</li>
<li>
@@ -245,15 +259,20 @@ export default class AdminSidebar extends React.Component {
<span className='icon fa fa-gear'></span>
<span>{'TEAMS (' + count + ')'}</span>
<span className='menu-icon--right'>
+ <OverlayTrigger
+ delayShow={1000}
+ placement='top'
+ overlay={addTeamTooltip}
+ >
<a
href='#'
onClick={this.showTeamSelect}
>
<i
className='fa fa-plus'
- title='Add team to sidebar menu'
></i>
</a>
+ </OverlayTrigger>
</span>
</h4>
</li>
diff --git a/web/react/components/admin_console/select_team_modal.jsx b/web/react/components/admin_console/select_team_modal.jsx
index 22189821b..858b6bbfe 100644
--- a/web/react/components/admin_console/select_team_modal.jsx
+++ b/web/react/components/admin_console/select_team_modal.jsx
@@ -57,7 +57,7 @@ export default class SelectTeamModal extends React.Component {
<select
ref='team'
size='10'
- style={{width: '100%'}}
+ className='form-control'
>
{options}
</select>
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 66d8c507a..b32656bfc 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -95,6 +95,10 @@ export default class Post extends React.Component {
return true;
}
+ if (nextProps.shouldHighlight !== this.props.shouldHighlight) {
+ return true;
+ }
+
return false;
}
getCommentCount(props) {
diff --git a/web/react/components/suggestion/emoticon_provider.jsx b/web/react/components/suggestion/emoticon_provider.jsx
new file mode 100644
index 000000000..7dcb86442
--- /dev/null
+++ b/web/react/components/suggestion/emoticon_provider.jsx
@@ -0,0 +1,74 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import SuggestionStore from '../../stores/suggestion_store.jsx';
+import * as Emoticons from '../../utils/emoticons.jsx';
+
+const MAX_EMOTICON_SUGGESTIONS = 40;
+
+class EmoticonSuggestion extends React.Component {
+ render() {
+ const text = this.props.term;
+ const name = this.props.item;
+
+ let className = 'emoticon-suggestion';
+ if (this.props.isSelection) {
+ className += ' suggestion--selected';
+ }
+
+ return (
+ <div
+ className={className}
+ onClick={this.props.onClick}
+ >
+ <div className='pull-left'>
+ <img
+ alt={text}
+ className='emoticon-suggestion__image'
+ src={Emoticons.getImagePathForEmoticon(name)}
+ title={text}
+ />
+ </div>
+ <div className='pull-left'>
+ {text}
+ </div>
+ </div>
+ );
+ }
+}
+
+EmoticonSuggestion.propTypes = {
+ item: React.PropTypes.string.isRequired,
+ term: React.PropTypes.string.isRequired,
+ isSelection: React.PropTypes.bool,
+ onClick: React.PropTypes.func
+};
+
+export default class EmoticonProvider {
+ handlePretextChanged(suggestionId, pretext) {
+ const captured = (/(?:^|\s)(:([a-zA-Z0-9_+\-]*))$/g).exec(pretext);
+ if (captured) {
+ const text = captured[1];
+ const partialName = captured[2];
+
+ const terms = [];
+ const names = [];
+
+ for (const emoticon of Emoticons.emoticonMap.keys()) {
+ if (emoticon.indexOf(partialName) !== -1) {
+ terms.push(':' + emoticon + ':');
+ names.push(emoticon);
+
+ if (terms.length >= MAX_EMOTICON_SUGGESTIONS) {
+ break;
+ }
+ }
+ }
+
+ if (terms.length > 0) {
+ SuggestionStore.setMatchedPretext(suggestionId, text);
+ SuggestionStore.addSuggestions(suggestionId, terms, names, EmoticonSuggestion);
+ }
+ }
+ }
+}
diff --git a/web/react/components/suggestion/search_suggestion_list.jsx b/web/react/components/suggestion/search_suggestion_list.jsx
index 542d28ddd..3378a33a0 100644
--- a/web/react/components/suggestion/search_suggestion_list.jsx
+++ b/web/react/components/suggestion/search_suggestion_list.jsx
@@ -35,7 +35,7 @@ export default class SearchSuggestionList extends SuggestionList {
}
render() {
- if (this.state.items.length === 0 || !this.props.show) {
+ if (this.state.items.length === 0) {
return null;
}
diff --git a/web/react/components/suggestion/suggestion_box.jsx b/web/react/components/suggestion/suggestion_box.jsx
index 4ca461e82..4cfb38f8e 100644
--- a/web/react/components/suggestion/suggestion_box.jsx
+++ b/web/react/components/suggestion/suggestion_box.jsx
@@ -13,7 +13,6 @@ export default class SuggestionBox extends React.Component {
super(props);
this.handleDocumentClick = this.handleDocumentClick.bind(this);
- this.handleFocus = this.handleFocus.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleCompleteWord = this.handleCompleteWord.bind(this);
@@ -21,10 +20,6 @@ export default class SuggestionBox extends React.Component {
this.handlePretextChanged = this.handlePretextChanged.bind(this);
this.suggestionId = Utils.generateId();
-
- this.state = {
- focused: false
- };
}
componentDidMount() {
@@ -49,27 +44,11 @@ export default class SuggestionBox extends React.Component {
}
handleDocumentClick(e) {
- if (!this.state.focused) {
- return;
- }
-
const container = $(ReactDOM.findDOMNode(this));
if (!(container.is(e.target) || container.has(e.target).length > 0)) {
// we can't just use blur for this because it fires and hides the children before
// their click handlers can be called
- this.setState({
- focused: false
- });
- }
- }
-
- handleFocus() {
- this.setState({
- focused: true
- });
-
- if (this.props.onFocus) {
- this.props.onFocus();
+ EventHelpers.emitClearSuggestions(this.suggestionId);
}
}
@@ -134,7 +113,6 @@ export default class SuggestionBox extends React.Component {
render() {
const newProps = Object.assign({}, this.props, {
- onFocus: this.handleFocus,
onChange: this.handleChange,
onKeyDown: this.handleKeyDown
});
@@ -162,10 +140,7 @@ export default class SuggestionBox extends React.Component {
return (
<div>
{textbox}
- <SuggestionListComponent
- suggestionId={this.suggestionId}
- show={this.state.focused}
- />
+ <SuggestionListComponent suggestionId={this.suggestionId} />
</div>
);
}
@@ -184,6 +159,5 @@ SuggestionBox.propTypes = {
// explicitly name any input event handlers we override and need to manually call
onChange: React.PropTypes.func,
- onKeyDown: React.PropTypes.func,
- onFocus: React.PropTypes.func
+ onKeyDown: React.PropTypes.func
};
diff --git a/web/react/components/suggestion/suggestion_list.jsx b/web/react/components/suggestion/suggestion_list.jsx
index 45843f4c8..e3ccd0f08 100644
--- a/web/react/components/suggestion/suggestion_list.jsx
+++ b/web/react/components/suggestion/suggestion_list.jsx
@@ -82,7 +82,7 @@ export default class SuggestionList extends React.Component {
}
render() {
- if (this.state.items.length === 0 || !this.props.show) {
+ if (this.state.items.length === 0) {
return null;
}
@@ -100,6 +100,7 @@ export default class SuggestionList extends React.Component {
key={term}
ref={term}
item={item}
+ term={term}
isSelection={isSelection}
onClick={this.handleItemClick.bind(this, term)}
/>
@@ -120,6 +121,5 @@ export default class SuggestionList extends React.Component {
}
SuggestionList.propTypes = {
- suggestionId: React.PropTypes.string.isRequired,
- show: React.PropTypes.bool.isRequired
+ suggestionId: React.PropTypes.string.isRequired
};
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 107e65f57..b29f304ab 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -3,6 +3,7 @@
import AtMentionProvider from './suggestion/at_mention_provider.jsx';
import CommandProvider from './suggestion/command_provider.jsx';
+import EmoticonProvider from './suggestion/emoticon_provider.jsx';
import SuggestionList from './suggestion/suggestion_list.jsx';
import SuggestionBox from './suggestion/suggestion_box.jsx';
import ErrorStore from '../stores/error_store.jsx';
@@ -29,7 +30,7 @@ export default class Textbox extends React.Component {
connection: ''
};
- this.suggestionProviders = [new AtMentionProvider()];
+ this.suggestionProviders = [new AtMentionProvider(), new EmoticonProvider()];
if (props.supportsCommands) {
this.suggestionProviders.push(new CommandProvider());
}
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index dc3865c68..c464258de 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -241,7 +241,7 @@ export default class UserSettingsDisplay extends React.Component {
const inputs = [
<div key='userDisplayNameOptions'>
<div
- className='input-group theme-group dropdown'
+ className='dropdown'
>
<select
className='form-control'
@@ -251,9 +251,6 @@ export default class UserSettingsDisplay extends React.Component {
>
{options}
</select>
- <span className={'input-group-addon ' + Constants.FONTS[this.state.selectedFont]}>
- {this.state.selectedFont}
- </span>
</div>
<div><br/>{'Select the font displayed in the Mattermost user interface.'}</div>
</div>
@@ -312,12 +309,12 @@ export default class UserSettingsDisplay extends React.Component {
<div className='user-settings'>
<h3 className='tab-header'>{'Display Settings'}</h3>
<div className='divider-dark first'/>
+ {fontSection}
+ <div className='divider-dark'/>
{clockSection}
<div className='divider-dark'/>
{nameFormatSection}
<div className='divider-dark'/>
- {fontSection}
- <div className='divider-dark'/>
</div>
</div>
);
diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx
index f792c610f..3deddd754 100644
--- a/web/react/dispatcher/event_helpers.jsx
+++ b/web/react/dispatcher/event_helpers.jsx
@@ -141,3 +141,10 @@ export function emitCompleteWordSuggestion(suggestionId, term = '') {
term
});
}
+
+export function emitClearSuggestions(suggestionId) {
+ AppDispatcher.handleViewAction({
+ type: Constants.ActionTypes.SUGGESTION_CLEAR_SUGGESTIONS,
+ id: suggestionId
+ });
+}
diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx
index 182f5810f..2250ec234 100644
--- a/web/react/stores/suggestion_store.jsx
+++ b/web/react/stores/suggestion_store.jsx
@@ -244,6 +244,11 @@ class SuggestionStore extends EventEmitter {
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);
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 8164095b9..b641e966b 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -55,6 +55,7 @@ export default {
SUGGESTION_PRETEXT_CHANGED: null,
SUGGESTION_RECEIVED_SUGGESTIONS: null,
+ SUGGESTION_CLEAR_SUGGESTIONS: null,
SUGGESTION_COMPLETE_WORD: null,
SUGGESTION_SELECT_NEXT: null,
SUGGESTION_SELECT_PREVIOUS: null
diff --git a/web/react/utils/emoticons.jsx b/web/react/utils/emoticons.jsx
index 8943e9544..ab04936c0 100644
--- a/web/react/utils/emoticons.jsx
+++ b/web/react/utils/emoticons.jsx
@@ -116,19 +116,19 @@ function initializeEmoticonMap() {
const out = new Map();
for (let i = 0; i < emoticonNames.length; i++) {
- out[emoticonNames[i]] = true;
+ out.set(emoticonNames[i], true);
}
return out;
}
-const emoticonMap = initializeEmoticonMap();
+export const emoticonMap = initializeEmoticonMap();
export function handleEmoticons(text, tokens) {
let output = text;
function replaceEmoticonWithToken(fullMatch, prefix, matchText, name) {
- if (emoticonMap[name]) {
+ if (emoticonMap.has(name)) {
const index = tokens.size;
const alias = `MM_EMOTICON${index}`;
@@ -159,4 +159,4 @@ export function getImagePathForEmoticon(name) {
return `/static/images/emoji/${name}.png`;
}
return `/static/images/emoji`;
-} \ No newline at end of file
+}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 788d8a45c..0a52f5b37 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -695,15 +695,19 @@ export function applyTheme(theme) {
}
export function applyFont(fontName) {
- const body = document.querySelector('body');
- const keys = Object.getOwnPropertyNames(body.classList);
- keys.forEach((k) => {
- const className = body.classList[k];
- if (className && className.lastIndexOf('font') === 0) {
- body.classList.remove(className);
+ const body = $('body');
+
+ for (const key of Reflect.ownKeys(Constants.FONTS)) {
+ const className = Constants.FONTS[key];
+
+ if (fontName === key) {
+ if (!body.hasClass(className)) {
+ body.addClass(className);
+ }
+ } else {
+ body.removeClass(className);
}
- });
- body.classList.add(Constants.FONTS[fontName]);
+ }
}
export function changeCss(className, classValue, classRepeat) {
@@ -1238,4 +1242,4 @@ export function getPostTerm(post) {
export function isFeatureEnabled(feature) {
return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label, {value: 'false'}).value === 'true';
-} \ No newline at end of file
+}