diff options
Diffstat (limited to 'webapp')
-rw-r--r-- | webapp/actions/channel_actions.jsx | 17 | ||||
-rw-r--r-- | webapp/components/admin_console/localization_settings.jsx | 2 | ||||
-rw-r--r-- | webapp/components/change_url_modal.jsx | 21 | ||||
-rw-r--r-- | webapp/components/new_channel_flow.jsx | 15 | ||||
-rw-r--r-- | webapp/components/new_channel_modal.jsx | 4 | ||||
-rw-r--r-- | webapp/components/suggestion/at_mention_provider.jsx | 110 | ||||
-rw-r--r-- | webapp/components/suggestion/suggestion_list.jsx | 21 | ||||
-rw-r--r-- | webapp/components/user_profile.jsx | 20 | ||||
-rw-r--r-- | webapp/components/user_settings/user_settings_general.jsx | 2 | ||||
-rw-r--r-- | webapp/components/user_settings/user_settings_security.jsx | 4 | ||||
-rw-r--r-- | webapp/i18n/en.json | 15 | ||||
-rw-r--r-- | webapp/sass/components/_suggestion-list.scss | 30 | ||||
-rw-r--r-- | webapp/sass/components/_tutorial.scss | 1 | ||||
-rw-r--r-- | webapp/sass/responsive/_mobile.scss | 8 | ||||
-rw-r--r-- | webapp/utils/async_client.jsx | 23 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 5 | ||||
-rw-r--r-- | webapp/utils/user_agent.jsx | 10 |
17 files changed, 227 insertions, 81 deletions
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() { |