summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/oauth_test.go4
-rw-r--r--webapp/actions/channel_actions.jsx17
-rw-r--r--webapp/components/admin_console/localization_settings.jsx2
-rw-r--r--webapp/components/change_url_modal.jsx21
-rw-r--r--webapp/components/new_channel_flow.jsx15
-rw-r--r--webapp/components/new_channel_modal.jsx4
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx110
-rw-r--r--webapp/components/suggestion/suggestion_list.jsx21
-rw-r--r--webapp/components/user_profile.jsx20
-rw-r--r--webapp/components/user_settings/user_settings_general.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx4
-rw-r--r--webapp/i18n/en.json15
-rw-r--r--webapp/sass/components/_suggestion-list.scss30
-rw-r--r--webapp/sass/components/_tutorial.scss1
-rw-r--r--webapp/sass/responsive/_mobile.scss8
-rw-r--r--webapp/utils/async_client.jsx23
-rw-r--r--webapp/utils/constants.jsx5
-rw-r--r--webapp/utils/user_agent.jsx10
18 files changed, 229 insertions, 83 deletions
diff --git a/api/oauth_test.go b/api/oauth_test.go
index b719e17cc..47fc342ce 100644
--- a/api/oauth_test.go
+++ b/api/oauth_test.go
@@ -193,8 +193,8 @@ func TestGetOAuthAppsByUser(t *testing.T) {
} else {
apps := result.Data.([]*model.OAuthApp)
- if len(apps) != 4 {
- t.Fatal("incorrect number of apps should have been 4")
+ if len(apps) < 4 {
+ t.Fatal("incorrect number of apps should have been 4 or more")
}
}
}
diff --git a/webapp/actions/channel_actions.jsx b/webapp/actions/channel_actions.jsx
index e8cd5aff0..c264fcb34 100644
--- a/webapp/actions/channel_actions.jsx
+++ b/webapp/actions/channel_actions.jsx
@@ -7,6 +7,7 @@ import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import Client from 'client/web_client.jsx';
export function goToChannel(channel) {
@@ -28,12 +29,16 @@ export function executeCommand(channelId, message, suggest, success, error) {
msg = msg.substring(0, msg.indexOf(' ')).toLowerCase() + msg.substring(msg.indexOf(' '), msg.length);
- if (message.indexOf('/shortcuts') !== -1 && Utils.isMac()) {
- msg += ' mac';
- }
-
- if (!Utils.isMac() && message.indexOf('/shortcuts') !== -1 && message.indexOf('mac') !== -1) {
- msg = '/shortcuts';
+ if (message.indexOf('/shortcuts') !== -1) {
+ if (UserAgent.isMobileApp()) {
+ const err = {message: Utils.localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')};
+ error(err);
+ return;
+ } else if (Utils.isMac()) {
+ msg += ' mac';
+ } else if (message.indexOf('mac') !== -1) {
+ msg = '/shortcuts';
+ }
}
Client.executeCommand(channelId, msg, suggest, success, error);
diff --git a/webapp/components/admin_console/localization_settings.jsx b/webapp/components/admin_console/localization_settings.jsx
index a9a0a8044..b5bc28b52 100644
--- a/webapp/components/admin_console/localization_settings.jsx
+++ b/webapp/components/admin_console/localization_settings.jsx
@@ -114,7 +114,7 @@ export default class LocalizationSettings extends AdminSettings {
helpText={
<FormattedHTMLMessage
id='admin.general.localization.availableLocalesDescription'
- defaultMessage='Set which languages are available for users in Account Settings (leave this field blank to have all supported languages available).<br /><br />Would like to help with translations? Join the <a href="http://translate.mattermost.com/" target="_blank">Mattermost Translation Server</a> to contribute.'
+ defaultMessage='Set which languages are available for users in Account Settings (leave this field blank to have all supported languages available). If you’re manually adding new languages, the <strong>Default Client Language</strong> must be added before saving this setting.<br /><br />Would like to help with translations? Join the <a href="http://translate.mattermost.com/" target="_blank">Mattermost Translation Server</a> to contribute.'
/>
}
noResultText={
diff --git a/webapp/components/change_url_modal.jsx b/webapp/components/change_url_modal.jsx
index 2219ff317..fa115cf36 100644
--- a/webapp/components/change_url_modal.jsx
+++ b/webapp/components/change_url_modal.jsx
@@ -119,16 +119,20 @@ export default class ChangeUrlModal extends React.Component {
}
render() {
let urlClass = 'input-group input-group--limit';
- let urlError = null;
- let serverError = null;
+ let error = null;
if (this.state.urlError) {
urlClass += ' has-error';
- urlError = (<p className='input__help error'>{this.state.urlError}</p>);
}
- if (this.props.serverError) {
- serverError = <div className='form-group has-error'><p className='input__help error'>{this.props.serverError}</p></div>;
+ if (this.props.serverError || this.state.urlError) {
+ error = (
+ <div className='form-group has-error'>
+ <p className='input__help error'>
+ {this.state.urlError || this.props.serverError}
+ </p>
+ </div>
+ );
}
const fullTeamUrl = TeamStore.getCurrentTeamUrl();
@@ -173,8 +177,7 @@ export default class ChangeUrlModal extends React.Component {
tabIndex='1'
/>
</div>
- {urlError}
- {serverError}
+ {error}
</div>
</div>
</Modal.Body>
@@ -211,7 +214,7 @@ ChangeUrlModal.defaultProps = {
urlLabel: 'URL',
submitButtonText: 'Save',
currentURL: '',
- serverError: ''
+ serverError: null
};
ChangeUrlModal.propTypes = {
@@ -221,7 +224,7 @@ ChangeUrlModal.propTypes = {
urlLabel: React.PropTypes.string,
submitButtonText: React.PropTypes.string,
currentURL: React.PropTypes.string,
- serverError: React.PropTypes.string,
+ serverError: React.PropTypes.node,
onModalSubmit: React.PropTypes.func.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};
diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx
index abec799b5..c6c265725 100644
--- a/webapp/components/new_channel_flow.jsx
+++ b/webapp/components/new_channel_flow.jsx
@@ -9,7 +9,7 @@ import UserStore from 'stores/user_store.jsx';
import NewChannelModal from './new_channel_modal.jsx';
import ChangeURLModal from './change_url_modal.jsx';
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
@@ -124,7 +124,16 @@ class NewChannelFlow extends React.Component {
},
(err) => {
if (err.id === 'model.channel.is_valid.2_or_more.app_error') {
- this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE});
+ this.setState({
+ flowState: SHOW_EDIT_URL_THEN_COMPLETE,
+ serverError: (
+ <FormattedMessage
+ id='channel_flow.handleTooShort'
+ defaultMessage='Channel URL must be 2 or more lowercase alphanumeric characters'
+ />
+ )
+ });
+ return;
}
if (err.id === 'store.sql_channel.update.exists.app_error') {
this.setState({serverError: Utils.localizeMessage('channel_flow.alreadyExist', 'A channel with that URL already exists')});
@@ -148,7 +157,7 @@ class NewChannelFlow extends React.Component {
if (this.state.flowState === SHOW_EDIT_URL_THEN_COMPLETE) {
this.setState({channelName: newURL, nameModified: true}, this.doSubmit);
} else {
- this.setState({flowState: SHOW_NEW_CHANNEL, serverError: '', channelName: newURL, nameModified: true});
+ this.setState({flowState: SHOW_NEW_CHANNEL, serverError: null, channelName: newURL, nameModified: true});
}
}
urlChangeDismissed() {
diff --git a/webapp/components/new_channel_modal.jsx b/webapp/components/new_channel_modal.jsx
index e174ddd32..31ed15306 100644
--- a/webapp/components/new_channel_modal.jsx
+++ b/webapp/components/new_channel_modal.jsx
@@ -372,14 +372,14 @@ class NewChannelModal extends React.Component {
NewChannelModal.defaultProps = {
show: false,
channelType: 'O',
- serverError: ''
+ serverError: null
};
NewChannelModal.propTypes = {
intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
channelData: React.PropTypes.object.isRequired,
- serverError: React.PropTypes.string,
+ serverError: React.PropTypes.node,
onSubmitChannel: React.PropTypes.func.isRequired,
onModalDismissed: React.PropTypes.func.isRequired,
onTypeSwitched: React.PropTypes.func.isRequired,
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index b9127e8d3..87cdc6894 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -8,6 +8,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
+import Constants from 'utils/constants.jsx';
import {FormattedMessage} from 'react-intl';
import Suggestion from './suggestion.jsx';
@@ -98,60 +99,91 @@ class AtMentionSuggestion extends Suggestion {
}
}
+function filterUsersByPrefix(users, prefix, limit) {
+ const filtered = [];
+
+ for (const id of Object.keys(users)) {
+ if (filtered.length >= limit) {
+ break;
+ }
+
+ const user = users[id];
+
+ if (user.delete_at > 0) {
+ continue;
+ }
+
+ if (user.username.startsWith(prefix) ||
+ (user.first_name && user.first_name.toLowerCase().startsWith(prefix)) ||
+ (user.last_name && user.last_name.toLowerCase().startsWith(prefix)) ||
+ (user.nickname && user.nickname.toLowerCase().startsWith(prefix))) {
+ filtered.push(user);
+ }
+ }
+
+ return filtered;
+}
+
export default class AtMentionProvider {
handlePretextChanged(suggestionId, pretext) {
const captured = (/@([a-z0-9\-\._]*)$/i).exec(pretext.toLowerCase());
if (captured) {
const prefix = captured[1];
+ // Group users into members and nonmembers of the channel.
const users = UserStore.getActiveOnlyProfiles(true);
-
- const filtered = [];
-
- for (const id of Object.keys(users)) {
- const user = users[id];
-
- if (user.delete_at > 0) {
- continue;
- }
-
- if (user.username.startsWith(prefix) ||
- (user.first_name && user.first_name.toLowerCase().startsWith(prefix)) ||
- (user.last_name && user.last_name.toLowerCase().startsWith(prefix)) ||
- (user.nickname && user.nickname.toLowerCase().startsWith(prefix))) {
- filtered.push(user);
- }
-
- if (filtered.length >= MaxUserSuggestions) {
- break;
+ const channelMembers = {};
+ const extra = ChannelStore.getCurrentExtraInfo();
+ for (let i = 0; i < extra.members.length; i++) {
+ const id = extra.members[i].id;
+ if (users[id]) {
+ channelMembers[id] = users[id];
+ Reflect.deleteProperty(users, id);
}
}
-
+ const channelNonmembers = users;
+
+ // Filter users by prefix.
+ const filteredMembers = filterUsersByPrefix(
+ channelMembers, prefix, MaxUserSuggestions);
+ const filteredNonmembers = filterUsersByPrefix(
+ channelNonmembers, prefix, MaxUserSuggestions - filteredMembers.length);
+ let filteredSpecialMentions = [];
if (!pretext.startsWith('/msg')) {
- // add dummy users to represent the @channel and @all special mentions when not using the /msg command
- if ('channel'.startsWith(prefix)) {
- filtered.push({username: 'channel'});
- }
- if ('all'.startsWith(prefix)) {
- filtered.push({username: 'all'});
- }
- if ('here'.startsWith(prefix)) {
- filtered.push({username: 'here'});
- }
+ filteredSpecialMentions = ['here', 'channel', 'all'].filter((item) => {
+ return item.startsWith(prefix);
+ }).map((name) => {
+ return {username: name};
+ });
}
- filtered.sort((a, b) => {
- const aPrefix = a.username.startsWith(prefix);
- const bPrefix = b.username.startsWith(prefix);
+ // Sort users by username.
+ [filteredMembers, filteredNonmembers].forEach((items) => {
+ items.sort((a, b) => {
+ const aPrefix = a.username.startsWith(prefix);
+ const bPrefix = b.username.startsWith(prefix);
- if (aPrefix === bPrefix) {
- return a.username.localeCompare(b.username);
- } else if (aPrefix) {
- return -1;
- }
+ if (aPrefix === bPrefix) {
+ return a.username.localeCompare(b.username);
+ } else if (aPrefix) {
+ return -1;
+ }
+
+ return 1;
+ });
+ });
- return 1;
+ filteredMembers.forEach((item) => {
+ item.type = Constants.MENTION_MEMBERS;
});
+ filteredNonmembers.forEach((item) => {
+ item.type = Constants.MENTION_NONMEMBERS;
+ });
+ filteredSpecialMentions.forEach((item) => {
+ item.type = Constants.MENTION_SPECIAL;
+ });
+
+ const filtered = filteredMembers.concat(filteredSpecialMentions).concat(filteredNonmembers);
const mentions = filtered.map((user) => '@' + user.username);
diff --git a/webapp/components/suggestion/suggestion_list.jsx b/webapp/components/suggestion/suggestion_list.jsx
index 52b85b2f5..7c746ac2a 100644
--- a/webapp/components/suggestion/suggestion_list.jsx
+++ b/webapp/components/suggestion/suggestion_list.jsx
@@ -5,6 +5,7 @@ import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as GlobalActions from 'actions/global_actions.jsx';
import SuggestionStore from 'stores/suggestion_store.jsx';
+import {FormattedMessage} from 'react-intl';
import React from 'react';
@@ -92,19 +93,39 @@ export default class SuggestionList extends React.Component {
}
}
+ renderDivider(type) {
+ return (
+ <div
+ key={type + '-divider'}
+ className='suggestion-list__divider'
+ >
+ <span>
+ <FormattedMessage id={'suggestion.' + type}/>
+ </span>
+ </div>
+ );
+ }
+
render() {
if (this.state.items.length === 0) {
return null;
}
const items = [];
+ let lastType;
for (let i = 0; i < this.state.items.length; i++) {
+ const item = this.state.items[i];
const term = this.state.terms[i];
const isSelection = term === this.state.selection;
// ReactComponent names need to be upper case when used in JSX
const Component = this.state.components[i];
+ if (item.type !== lastType) {
+ items.push(this.renderDivider(item.type));
+ lastType = item.type;
+ }
+
items.push(
<Component
key={term}
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index c7020fed9..bc542165a 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -79,6 +79,24 @@ export default class UserProfile extends React.Component {
/>
);
+ let fullname = Utils.getFullName(this.props.user);
+ if (fullname) {
+ dataContent.push(
+ <div
+ data-toggle='tooltip'
+ title={fullname}
+ key='user-popover-fullname'
+ >
+
+ <p
+ className='text-nowrap'
+ >
+ {fullname}
+ </p>
+ </div>
+ );
+ }
+
if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
dataContent.push(
<div
@@ -103,7 +121,7 @@ export default class UserProfile extends React.Component {
rootClose={true}
overlay={
<Popover
- title={name}
+ title={'@' + this.props.user.username}
id='user-profile-popover'
>
{dataContent}
diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx
index d1c195c7e..e725060ab 100644
--- a/webapp/components/user_settings/user_settings_general.jsx
+++ b/webapp/components/user_settings/user_settings_general.jsx
@@ -27,7 +27,7 @@ const holders = defineMessages({
},
validEmail: {
id: 'user.settings.general.validEmail',
- defaultMessage: 'Please enter a valid email address'
+ defaultMessage: 'Please enter a valid email address.'
},
emailMatch: {
id: 'user.settings.general.emailMatch',
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index 428c88e25..769959432 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -22,7 +22,7 @@ import {Link} from 'react-router/es6';
const holders = defineMessages({
currentPasswordError: {
id: 'user.settings.security.currentPasswordError',
- defaultMessage: 'Please enter your current password'
+ defaultMessage: 'Please enter your current password.'
},
passwordLengthError: {
id: 'user.settings.security.passwordLengthError',
@@ -30,7 +30,7 @@ const holders = defineMessages({
},
passwordMatchError: {
id: 'user.settings.security.passwordMatchError',
- defaultMessage: 'The new passwords you entered do not match'
+ defaultMessage: 'The new passwords you entered do not match.'
},
method: {
id: 'user.settings.security.method',
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index b37b7a5d5..72733c60b 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -282,7 +282,7 @@
"admin.files.storage": "Storage",
"admin.general.configuration": "Configuration",
"admin.general.localization": "Localization",
- "admin.general.localization.availableLocalesDescription": "Set which languages are available for users in Account Settings (leave this field blank to have all supported languages available).<br /><br />Would like to help with translations? Join the <a href='http://translate.mattermost.com/' target='_blank'>Mattermost Translation Server</a> to contribute.",
+ "admin.general.localization.availableLocalesDescription": "Set which languages are available for users in Account Settings (leave this field blank to have all supported languages available). If you’re manually adding new languages, the <strong>Default Client Language</strong> must be added before saving this setting.<br /><br />Would like to help with translations? Join the <a href='http://translate.mattermost.com/' target='_blank'>Mattermost Translation Server</a> to contribute.",
"admin.general.localization.availableLocalesTitle": "Available Languages:",
"admin.general.localization.clientLocaleDescription": "Default language for newly created users and pages where the user hasn't logged in.",
"admin.general.localization.clientLocaleTitle": "Default Client Language:",
@@ -919,6 +919,7 @@
"channel_flow.channel": "Channel",
"channel_flow.create": "Create {term}",
"channel_flow.group": "Group",
+ "channel_flow.handleTooShort": "Channel URL must be 2 or more lowercase alphanumeric characters",
"channel_flow.invalidName": "Invalid Channel Name",
"channel_flow.set_url_title": "Set {term} URL",
"channel_header.channel": "Channel",
@@ -1041,6 +1042,9 @@
"create_comment.files": "Files uploading",
"create_post.comment": "Comment",
"create_post.post": "Post",
+ "create_post.shortcutsNotSupported": "Keyboard shortcuts are not supported on your device.",
+ "flag_post.flag": "Flag for follow up",
+ "flag_post.unflag": "Unflag",
"create_post.tutorialTip": "<h4>Sending Messages</h4><p>Type here to write a message and press <strong>Enter</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>",
"create_post.write": "Write a message...",
"create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
@@ -1097,7 +1101,7 @@
"edit_post.save": "Save",
"email_signup.address": "Email Address",
"email_signup.createTeam": "Create Team",
- "email_signup.emailError": "Please enter a valid email address",
+ "email_signup.emailError": "Please enter a valid email address.",
"email_signup.find": "Find my teams",
"email_verify.almost": "{siteName}: You are almost done",
"email_verify.failed": " Failed to send verification email.",
@@ -1534,6 +1538,9 @@
"suggestion.mention.all": "Notifies everyone in the channel, use in {townsquare} to notify the whole team",
"suggestion.mention.channel": "Notifies everyone in the channel",
"suggestion.mention.here": "Notifies everyone in the channel and online",
+ "suggestion.mention.members": "Channel Members",
+ "suggestion.mention.nonmembers": "Not in Channel",
+ "suggestion.mention.special": "Special Mentions",
"suggestion.search.private": "Private Groups",
"suggestion.search.public": "Public Channels",
"team_export_tab.download": "download",
@@ -1771,7 +1778,7 @@
"user.settings.push_notification.onlyMentions": "For mentions and direct messages",
"user.settings.security.close": "Close",
"user.settings.security.currentPassword": "Current Password",
- "user.settings.security.currentPasswordError": "Please enter your current password",
+ "user.settings.security.currentPasswordError": "Please enter your current password.",
"user.settings.security.emailPwd": "Email and Password",
"user.settings.security.gitlab": "GitLab",
"user.settings.security.google": "Google",
@@ -1803,7 +1810,7 @@
"user.settings.security.passwordErrorUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
"user.settings.security.passwordLdapCantUpdate": "Login occurs through LDAP. Password cannot be updated.",
- "user.settings.security.passwordMatchError": "The new passwords you entered do not match",
+ "user.settings.security.passwordMatchError": "The new passwords you entered do not match.",
"user.settings.security.passwordMinLength": "Invalid minimum length, cannot show preview.",
"user.settings.security.retypePassword": "Retype New Password",
"user.settings.security.saml": "SAML",
diff --git a/webapp/sass/components/_suggestion-list.scss b/webapp/sass/components/_suggestion-list.scss
index 8bee49ce6..c995d5ebf 100644
--- a/webapp/sass/components/_suggestion-list.scss
+++ b/webapp/sass/components/_suggestion-list.scss
@@ -44,3 +44,33 @@
.suggestion-list--bottom {
position: relative;
}
+
+.suggestion-list__divider {
+ line-height: 21px;
+ margin: 5px 0px 0px 5px;
+ position: relative;
+
+ &:first-child {
+ margin-top: 5px;
+ }
+
+ > span {
+ color: rgba(51,51,51,0.7);
+ background: #f2f4f8;
+ display: inline-block;
+ padding-right: 10px;
+ position: relative;
+ z-index: 5;
+ }
+
+ &:before {
+ @include opacity(.2);
+ background: $dark-gray;
+ content: '';
+ height: 1px;
+ left: 0;
+ position: absolute;
+ top: 10px;
+ width: 100%;
+ }
+}
diff --git a/webapp/sass/components/_tutorial.scss b/webapp/sass/components/_tutorial.scss
index a8d3ffdf6..cd972c80c 100644
--- a/webapp/sass/components/_tutorial.scss
+++ b/webapp/sass/components/_tutorial.scss
@@ -176,7 +176,6 @@
.tutorial__content {
display: table-cell;
- padding: 20px 40px 80px;
vertical-align: middle;
.tutorial__steps {
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index defd1f65e..bda11dc2c 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -1,6 +1,14 @@
@charset 'UTF-8';
@media screen and (max-width: 768px) {
+ .tutorial-steps__container {
+ .tutorial__content {
+ .tutorial__steps {
+ margin-bottom: 0;
+ }
+ }
+ }
+
.prompt {
.prompt__heading {
display: block;
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index babfefb6d..185fa5b4e 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -10,6 +10,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as utils from './utils.jsx';
+import * as UserAgent from './user_agent.jsx';
import ErrorStore from 'stores/error_store.jsx';
import Constants from './constants.jsx';
@@ -871,17 +872,19 @@ export function getSuggestedCommands(command, suggestionId, component) {
(data) => {
var matches = [];
data.forEach((cmd) => {
- if (('/' + cmd.trigger).indexOf(command) === 0) {
- const s = '/' + cmd.trigger;
- let hint = '';
- if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
- hint = cmd.auto_complete_hint;
+ if (cmd.trigger !== 'shortcuts' || !UserAgent.isMobileApp()) {
+ if (('/' + cmd.trigger).indexOf(command) === 0) {
+ const s = '/' + cmd.trigger;
+ let hint = '';
+ if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
+ hint = cmd.auto_complete_hint;
+ }
+ matches.push({
+ suggestion: s,
+ hint,
+ description: cmd.auto_complete_desc
+ });
}
- matches.push({
- suggestion: s,
- hint,
- description: cmd.auto_complete_desc
- });
}
});
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index dbdc3e9f1..24d1d6b19 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -797,7 +797,10 @@ export const Constants = {
LICENSE_GRACE_PERIOD: 1000 * 60 * 60 * 24 * 15, // 15 days
PERMISSIONS_ALL: 'all',
PERMISSIONS_TEAM_ADMIN: 'team_admin',
- PERMISSIONS_SYSTEM_ADMIN: 'system_admin'
+ PERMISSIONS_SYSTEM_ADMIN: 'system_admin',
+ MENTION_MEMBERS: 'mention.members',
+ MENTION_NONMEMBERS: 'mention.nonmembers',
+ MENTION_SPECIAL: 'mention.special'
};
export default Constants;
diff --git a/webapp/utils/user_agent.jsx b/webapp/utils/user_agent.jsx
index 657718627..dbabd594b 100644
--- a/webapp/utils/user_agent.jsx
+++ b/webapp/utils/user_agent.jsx
@@ -61,6 +61,14 @@ export function isIosWeb() {
return isIosSafari() || isIosChrome();
}
+export function isIos() {
+ return userAgent.indexOf('iPhone') !== -1;
+}
+
+export function isAndroid() {
+ return userAgent.indexOf('Android') !== -1;
+}
+
export function isAndroidChrome() {
return userAgent.indexOf('Android') !== -1 && userAgent.indexOf('Chrome') !== -1 && userAgent.indexOf('Version') === -1;
}
@@ -70,7 +78,7 @@ export function isAndroidWeb() {
}
export function isMobileApp() {
- return userAgent.indexOf('iPhone') !== -1 && userAgent.indexOf('Safari') === -1 && userAgent.indexOf('CriOS') === -1;
+ return isAndroid() || isIos();
}
export function isFirefox() {