summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-08-03 00:01:33 -0400
committerCorey Hulen <corey@hulen.com>2016-08-02 20:01:33 -0800
commit1de3bd3b4340cb51b2699a14a92d653db988a988 (patch)
tree604703f9acf4ea3b71f30bdfa532020e9fb233be /webapp
parent790dd91e7d93b5debaf86915c34f8a73a3ff95bd (diff)
downloadchat-1de3bd3b4340cb51b2699a14a92d653db988a988.tar.gz
chat-1de3bd3b4340cb51b2699a14a92d653db988a988.tar.bz2
chat-1de3bd3b4340cb51b2699a14a92d653db988a988.zip
PLT-3640 Add mobile landing pages (#3674)
* PLT-3640 Moved all clientside user agent snooping into a single file * PLT-3640 Added mobile landing pages on login to iOS and Android web apps * PLT-3640 Moved landing page to appear before first login * PLT-3640 Fixed detection of Chrome on Android * PLT-3640 Disabled mobile landing pages when their respective URLs are set to blank
Diffstat (limited to 'webapp')
-rw-r--r--webapp/components/file_upload.jsx5
-rw-r--r--webapp/components/get_android_app/get_android_app.jsx75
-rw-r--r--webapp/components/get_ios_app/get_ios_app.jsx68
-rw-r--r--webapp/components/new_channel_modal.jsx5
-rw-r--r--webapp/components/post_view/components/post_list.jsx3
-rw-r--r--webapp/components/select_team/select_team.jsx3
-rw-r--r--webapp/components/settings_sidebar.jsx4
-rw-r--r--webapp/components/sidebar_right_menu.jsx3
-rw-r--r--webapp/components/user_settings/user_settings_notifications.jsx3
-rw-r--r--webapp/i18n/en.json9
-rw-r--r--webapp/images/app-store-button.pngbin0 -> 11277 bytes
-rw-r--r--webapp/images/iphone-6-mockup.pngbin0 -> 77346 bytes
-rw-r--r--webapp/images/nexus-6p-mockup.pngbin0 -> 60764 bytes
-rw-r--r--webapp/routes/route_root.jsx29
-rw-r--r--webapp/sass/routes/_get-app.scss96
-rw-r--r--webapp/sass/routes/_module.scss1
-rw-r--r--webapp/stores/browser_store.jsx39
-rw-r--r--webapp/utils/user_agent.jsx86
-rw-r--r--webapp/utils/utils.jsx51
19 files changed, 398 insertions, 82 deletions
diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx
index 088e8bde7..39abec7e4 100644
--- a/webapp/components/file_upload.jsx
+++ b/webapp/components/file_upload.jsx
@@ -8,6 +8,7 @@ import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import DelayedAction from 'utils/delayed_action.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages} from 'react-intl';
@@ -311,13 +312,13 @@ class FileUpload extends React.Component {
render() {
let multiple = true;
- if (Utils.isMobileApp()) {
+ if (UserAgent.isMobileApp()) {
// iOS WebViews don't upload videos properly in multiple mode
multiple = false;
}
let accept = '';
- if (Utils.isIosChrome()) {
+ if (UserAgent.isIosChrome()) {
// iOS Chrome can't upload videos at all
accept = 'image/*';
}
diff --git a/webapp/components/get_android_app/get_android_app.jsx b/webapp/components/get_android_app/get_android_app.jsx
new file mode 100644
index 000000000..ab73141b1
--- /dev/null
+++ b/webapp/components/get_android_app/get_android_app.jsx
@@ -0,0 +1,75 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {FormattedMessage} from 'react-intl';
+import {Link} from 'react-router';
+
+import MattermostIcon from 'images/favicon/android-chrome-192x192.png';
+import Nexus6Mockup from 'images/nexus-6p-mockup.png';
+
+export default class GetAndroidApp extends React.Component {
+ render() {
+ return (
+ <div className='get-app get-android-app'>
+ <h1 className='get-app__header'>
+ <FormattedMessage
+ id='get_app.androidHeader'
+ defaultMessage='Mattermost works best if you switch to our Android app'
+ />
+ </h1>
+ <hr/>
+ <div>
+ <img
+ className='get-android-app__icon'
+ src={MattermostIcon}
+ />
+ <div className='get-android-app__app-info'>
+ <span className='get-android-app__app-name'>
+ <FormattedMessage
+ id='get_app.androidAppName'
+ defaultMessage='Mattermost for Android'
+ />
+ </span>
+ <span className='get-android-app__app-creator'>
+ <FormattedMessage
+ id='get_app.mattermostInc'
+ defaultMessage='Mattermost, Inc'
+ />
+ </span>
+ </div>
+ </div>
+ <a
+ className='btn btn-primary get-android-app__continue'
+ href={global.window.mm_config.AndroidAppDownloadLink}
+ >
+ <FormattedMessage
+ id='get_app.continue'
+ defaultMessage='Continue'
+ />
+ </a>
+ <img
+ className='get-app__screenshot'
+ src={Nexus6Mockup}
+ />
+ <span className='get-app__continue-with-browser'>
+ <FormattedMessage
+ id='get_app.continueWithBrowser'
+ defaultMessage='Or {link}'
+ values={{
+ link: (
+ <Link to='/switch_team'>
+ <FormattedMessage
+ id='get_app.continueWithBrowserLink'
+ defaultMessage='continue with browser'
+ />
+ </Link>
+ )
+ }}
+ />
+ </span>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/webapp/components/get_ios_app/get_ios_app.jsx b/webapp/components/get_ios_app/get_ios_app.jsx
new file mode 100644
index 000000000..0980b5882
--- /dev/null
+++ b/webapp/components/get_ios_app/get_ios_app.jsx
@@ -0,0 +1,68 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {FormattedMessage} from 'react-intl';
+import {Link} from 'react-router';
+
+import AppStoreButton from 'images/app-store-button.png';
+import IPhone6Mockup from 'images/iphone-6-mockup.png';
+
+export default class GetIosApp extends React.Component {
+ render() {
+ return (
+ <div className='get-app get-ios-app'>
+ <h1 className='get-app__header'>
+ <FormattedMessage
+ id='get_app.iosHeader'
+ defaultMessage='Mattermost works best if you switch to our iPhone app'
+ />
+ </h1>
+ <hr/>
+ <a
+ className='get-ios-app__app-store-link'
+ href={global.window.mm_config.IosAppDownloadLink}
+ rel='noopener noreferrer'
+ >
+ <img src={AppStoreButton}/>
+ </a>
+ <img
+ className='get-app__screenshot'
+ src={IPhone6Mockup}
+ />
+ <h2 className='get-ios-app__already-have-it'>
+ <FormattedMessage
+ id='get_app.alreadyHaveIt'
+ defaultMessage='Already have it?'
+ />
+ </h2>
+ <a
+ className='btn btn-primary get-ios-app__open-mattermost'
+ href='mattermost://'
+ >
+ <FormattedMessage
+ id='get_app.openMattermost'
+ defaultMessage='Open Mattermost'
+ />
+ </a>
+ <span className='get-app__continue-with-browser'>
+ <FormattedMessage
+ id='get_app.continueWithBrowser'
+ defaultMessage='Or {link}'
+ values={{
+ link: (
+ <Link to='/switch_team'>
+ <FormattedMessage
+ id='get_app.continueWithBrowserLink'
+ defaultMessage='continue with browser'
+ />
+ </Link>
+ )
+ }}
+ />
+ </span>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/webapp/components/new_channel_modal.jsx b/webapp/components/new_channel_modal.jsx
index 1198335ca..e174ddd32 100644
--- a/webapp/components/new_channel_modal.jsx
+++ b/webapp/components/new_channel_modal.jsx
@@ -4,6 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -53,9 +54,11 @@ class NewChannelModal extends React.Component {
}
componentDidMount() {
- if (Utils.isBrowserIE()) {
+ // ???
+ if (UserAgent.isInternetExplorer()) {
$('body').addClass('browser--ie');
}
+
PreferenceStore.addChangeListener(this.onPreferenceChange);
}
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index 9f958a5b6..befd1a10d 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -11,6 +11,7 @@ import * as GlobalActions from 'actions/global_actions.jsx';
import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import DelayedAction from 'utils/delayed_action.jsx';
@@ -336,7 +337,7 @@ export default class PostList extends React.Component {
// Temporary fix to solve ie11 rendering issue
let newSeparatorId = '';
- if (!Utils.isBrowserIE()) {
+ if (!UserAgent.isInternetExplorer()) {
newSeparatorId = 'new_message_' + post.id;
}
postCtls.push(
diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx
index f1816238b..25a056954 100644
--- a/webapp/components/select_team/select_team.jsx
+++ b/webapp/components/select_team/select_team.jsx
@@ -3,6 +3,7 @@
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import ErrorBar from 'components/error_bar.jsx';
import LoadingScreen from 'components/loading_screen.jsx';
@@ -176,7 +177,7 @@ export default class SelectTeam extends React.Component {
}
let teamSignUp;
- if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp())) {
+ if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !UserAgent.isMobileApp())) {
teamSignUp = (
<div className='margin--extra'>
<Link
diff --git a/webapp/components/settings_sidebar.jsx b/webapp/components/settings_sidebar.jsx
index dc59409a0..3ccd372f7 100644
--- a/webapp/components/settings_sidebar.jsx
+++ b/webapp/components/settings_sidebar.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import $ from 'jquery';
-import * as Utils from 'utils/utils.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import React from 'react';
@@ -18,7 +18,7 @@ export default class SettingsSidebar extends React.Component {
$(e.target).closest('.settings-modal').addClass('display--content');
}
componentDidMount() {
- if (Utils.isBrowserFirefox()) {
+ if (UserAgent.isFirefox()) {
$('.settings-modal .settings-table .nav').addClass('position--top');
}
}
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
index 27e7c25d4..b36255a01 100644
--- a/webapp/components/sidebar_right_menu.jsx
+++ b/webapp/components/sidebar_right_menu.jsx
@@ -12,6 +12,7 @@ import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -296,7 +297,7 @@ export default class SidebarRightMenu extends React.Component {
}
let nativeAppLink = null;
- if (global.window.mm_config.AppDownloadLink && !Utils.isMobileApp()) {
+ if (global.window.mm_config.AppDownloadLink && !UserAgent.isMobileApp()) {
nativeAppLink = (
<li>
<Link
diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx
index e116ea7c4..336e614f9 100644
--- a/webapp/components/user_settings/user_settings_notifications.jsx
+++ b/webapp/components/user_settings/user_settings_notifications.jsx
@@ -9,13 +9,14 @@ import UserStore from 'stores/user_store.jsx';
import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
- var soundNeeded = !Utils.isBrowserFirefox();
+ var soundNeeded = !UserAgent.isFirefox();
var sound = 'true';
if (user.notify_props && user.notify_props.desktop_sound) {
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index a3f5d55f9..e2f14fa36 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -1112,6 +1112,15 @@
"general_tab.teamNameInfo": "Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.",
"general_tab.title": "General Settings",
"general_tab.yes": "Yes",
+ "get_app.alreadyHaveIt": "Already have it?",
+ "get_app.androidAppName": "Mattermost for Android",
+ "get_app.androidHeader": "Mattermost works best if you switch to our Android app",
+ "get_app.continue": "continue",
+ "get_app.continueWithBrowser": "Or {link}",
+ "get_app.continueWithBrowserLink": "continue with browser",
+ "get_app.iosHeader": "Mattermost works best if you switch to our iPhone app",
+ "get_app.mattermostInc": "Mattermost, Inc",
+ "get_app.openMattermost": "Open Mattermost",
"get_link.clipboard": " Link copied to clipboard.",
"get_link.close": "Close",
"get_link.copy": "Copy Link",
diff --git a/webapp/images/app-store-button.png b/webapp/images/app-store-button.png
new file mode 100644
index 000000000..66ab3eb4c
--- /dev/null
+++ b/webapp/images/app-store-button.png
Binary files differ
diff --git a/webapp/images/iphone-6-mockup.png b/webapp/images/iphone-6-mockup.png
new file mode 100644
index 000000000..544e40ea7
--- /dev/null
+++ b/webapp/images/iphone-6-mockup.png
Binary files differ
diff --git a/webapp/images/nexus-6p-mockup.png b/webapp/images/nexus-6p-mockup.png
new file mode 100644
index 000000000..63161cdf1
--- /dev/null
+++ b/webapp/images/nexus-6p-mockup.png
Binary files differ
diff --git a/webapp/routes/route_root.jsx b/webapp/routes/route_root.jsx
index 6593e2bd8..88c94b54b 100644
--- a/webapp/routes/route_root.jsx
+++ b/webapp/routes/route_root.jsx
@@ -9,7 +9,23 @@ import claimAccountRoute from 'routes/route_claim.jsx';
import createTeamRoute from 'routes/route_create_team.jsx';
import teamRoute from 'routes/route_team.jsx';
+import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
+
+function preLogin(nextState, replace, callback) {
+ // redirect to the mobile landing page if the user hasn't seen it before
+ if (window.mm_config.IosAppDownloadLink && UserAgent.isIosWeb() && !BrowserStore.hasSeenLandingPage()) {
+ replace('/get_ios_app');
+ BrowserStore.setLandingPageSeen(true);
+ } else if (window.mm_config.AndroidAppDownloadLink && UserAgent.isAndroidWeb() && !BrowserStore.hasSeenLandingPage()) {
+ replace('/get_android_app');
+ BrowserStore.setLandingPageSeen(true);
+ }
+
+ callback();
+}
+
function preLoggedIn(nextState, replace, callback) {
ErrorStore.clearLastError();
callback();
@@ -28,6 +44,7 @@ export default {
[
{
path: 'login',
+ onEnter: preLogin,
getComponents: (location, callback) => {
System.import('components/login/login_controller.jsx').then(RouteUtils.importComponentSuccess(callback));
}
@@ -67,6 +84,18 @@ export default {
)
},
{
+ path: 'get_ios_app',
+ getComponents: (location, callback) => {
+ System.import('components/get_ios_app/get_ios_app.jsx').then(RouteUtils.importComponentSuccess(callback));
+ }
+ },
+ {
+ path: 'get_android_app',
+ getComponents: (location, callback) => {
+ System.import('components/get_android_app/get_android_app.jsx').then(RouteUtils.importComponentSuccess(callback));
+ }
+ },
+ {
path: 'error',
getComponents: (location, callback) => {
System.import('components/error_page.jsx').then(RouteUtils.importComponentSuccess(callback));
diff --git a/webapp/sass/routes/_get-app.scss b/webapp/sass/routes/_get-app.scss
new file mode 100644
index 000000000..88797d053
--- /dev/null
+++ b/webapp/sass/routes/_get-app.scss
@@ -0,0 +1,96 @@
+.get-app {
+ hr {
+ border-top: 1px solid #ddd;
+ }
+
+ .get-app__header {
+ color: #666;
+ font-size: 20px;
+ font-weight: bold;
+ text-align: center
+ }
+
+ .get-app__screenshot {
+ border-bottom: 1px solid #ddd;
+ display: block;
+ margin: auto;
+ }
+
+ .get-app__continue-with-browser {
+ display: block;
+ margin-top: 40px;
+ text-align: center;
+ }
+}
+
+.get-android-app {
+ margin: 20px;
+
+ .get-app__header {
+ text-align: left;
+ }
+
+ .get-android-app__icon {
+ width: 60px;
+ }
+
+ .get-android-app__app-info {
+ display: inline-block;
+ margin-left: 8px;
+ vertical-align: middle;
+
+ .get-android-app__app-name {
+ color: #666;
+ display: block;
+ font-size: 13px;
+ font-weight: bold;
+ }
+
+ .get-android-app__app-creator {
+ color: #aaa;
+ display: block;
+ font-size: 10px
+ }
+ }
+
+ .get-app__screenshot {
+ width: 240px;
+ }
+
+ .get-android-app__continue {
+ display: block;
+ font-size: 16px;
+ margin-bottom: 40px;
+ margin-top: 15px;
+ padding: 12px;
+ }
+}
+
+.get-ios-app {
+ margin: 30px;
+
+ .get-app__screenshot {
+ width: 180px;
+ }
+
+ .get-ios-app__app-store-link {
+ display: block;
+ margin: auto;
+ margin-bottom: 30px;
+ width: 180px;
+ }
+
+ .get-ios-app__already-have-it {
+ font-size: 18px;
+ margin-bottom: 20px;
+ text-align: center;
+ }
+
+ .get-ios-app__open-mattermost {
+ display: block;
+ font-size: 20px;
+ margin: auto;
+ padding: 12px;
+ width: 220px;
+ }
+} \ No newline at end of file
diff --git a/webapp/sass/routes/_module.scss b/webapp/sass/routes/_module.scss
index 11b815007..c0a5b19bc 100644
--- a/webapp/sass/routes/_module.scss
+++ b/webapp/sass/routes/_module.scss
@@ -7,6 +7,7 @@
@import 'compliance';
@import 'docs';
@import 'error-page';
+@import 'get-app';
@import 'loading';
@import 'print';
@import 'settings';
diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx
index f19e5b9a1..9acd8530c 100644
--- a/webapp/stores/browser_store.jsx
+++ b/webapp/stores/browser_store.jsx
@@ -20,27 +20,6 @@ function getPrefix() {
}
class BrowserStoreClass {
- constructor() {
- this.getItem = this.getItem.bind(this);
- this.setItem = this.setItem.bind(this);
- this.removeItem = this.removeItem.bind(this);
- this.setGlobalItem = this.setGlobalItem.bind(this);
- this.getGlobalItem = this.getGlobalItem.bind(this);
- this.removeGlobalItem = this.removeGlobalItem.bind(this);
- this.actionOnItemsWithPrefix = this.actionOnItemsWithPrefix.bind(this);
- this.actionOnGlobalItemsWithPrefix = this.actionOnGlobalItemsWithPrefix.bind(this);
- this.isLocalStorageSupported = this.isLocalStorageSupported.bind(this);
- this.getLastServerVersion = this.getLastServerVersion.bind(this);
- this.setLastServerVersion = this.setLastServerVersion.bind(this);
- this.clear = this.clear.bind(this);
- this.clearAll = this.clearAll.bind(this);
- this.checkedLocalStorageSupported = '';
- this.signalLogout = this.signalLogout.bind(this);
- this.isSignallingLogout = this.isSignallingLogout.bind(this);
- this.signalLogin = this.signalLogin.bind(this);
- this.isSignallingLogin = this.isSignallingLogin.bind(this);
- }
-
setItem(name, value) {
this.setGlobalItem(getPrefix() + name, value);
}
@@ -162,9 +141,10 @@ class BrowserStoreClass {
}
clear() {
- // don't clear the logout id so IE11 can tell which tab sent a logout request
+ // persist some values through logout since they're independent of which user is logged in
const logoutId = sessionStorage.getItem('__logout__');
const serverVersion = this.getLastServerVersion();
+ const landingPageSeen = this.hasSeenLandingPage();
sessionStorage.clear();
localStorage.clear();
@@ -176,11 +156,10 @@ class BrowserStoreClass {
if (serverVersion) {
this.setLastServerVersion(serverVersion);
}
- }
- clearAll() {
- sessionStorage.clear();
- localStorage.clear();
+ if (landingPageSeen) {
+ this.setLandingPageSeen(landingPageSeen);
+ }
}
isLocalStorageSupported() {
@@ -210,6 +189,14 @@ class BrowserStoreClass {
return this.checkedLocalStorageSupported;
}
+
+ hasSeenLandingPage() {
+ return JSON.parse(sessionStorage.getItem('__landingPageSeen__'));
+ }
+
+ setLandingPageSeen(landingPageSeen) {
+ return sessionStorage.setItem('__landingPageSeen__', JSON.stringify(landingPageSeen));
+ }
}
var BrowserStore = new BrowserStoreClass();
diff --git a/webapp/utils/user_agent.jsx b/webapp/utils/user_agent.jsx
new file mode 100644
index 000000000..657718627
--- /dev/null
+++ b/webapp/utils/user_agent.jsx
@@ -0,0 +1,86 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+/*
+Example User Agents
+--------------------
+
+Chrome:
+ Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
+
+Firefox:
+ Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
+
+IE11:
+ Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
+
+Edge:
+ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
+
+Desktop App:
+ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Mattermost/1.2.1 Chrome/49.0.2623.75 Electron/0.37.8 Safari/537.36
+ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
+
+Android Chrome:
+ Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19
+
+Android App:
+ Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30
+ Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
+ Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/537.36
+
+iOS Safari:
+ Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3
+
+iOS Android:
+ Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3
+
+iOS App:
+ Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13F69
+*/
+
+const userAgent = window.navigator.userAgent;
+
+export function isChrome() {
+ return userAgent.indexOf('Chrome') > -1;
+}
+
+export function isSafari() {
+ return userAgent.indexOf('Safari') !== -1 && userAgent.indexOf('Chrome') === -1;
+}
+
+export function isIosSafari() {
+ return userAgent.indexOf('iPhone') !== -1 && userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('CriOS') === -1;
+}
+
+export function isIosChrome() {
+ return userAgent.indexOf('CriOS') !== -1;
+}
+
+export function isIosWeb() {
+ return isIosSafari() || isIosChrome();
+}
+
+export function isAndroidChrome() {
+ return userAgent.indexOf('Android') !== -1 && userAgent.indexOf('Chrome') !== -1 && userAgent.indexOf('Version') === -1;
+}
+
+export function isAndroidWeb() {
+ return isAndroidChrome();
+}
+
+export function isMobileApp() {
+ return userAgent.indexOf('iPhone') !== -1 && userAgent.indexOf('Safari') === -1 && userAgent.indexOf('CriOS') === -1;
+}
+
+export function isFirefox() {
+ return userAgent.indexOf('Firefox') !== -1;
+}
+
+export function isInternetExplorer() {
+ return userAgent.indexOf('Trident') !== -1;
+}
+
+export function isEdge() {
+ return userAgent.indexOf('Edge') !== -1;
+} \ No newline at end of file
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 4b3c8518c..187c7d7f4 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -12,6 +12,7 @@ import Constants from 'utils/constants.jsx';
var ActionTypes = Constants.ActionTypes;
import * as AsyncClient from './async_client.jsx';
import Client from 'client/web_client.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import {browserHistory} from 'react-router/es6';
import {FormattedMessage} from 'react-intl';
@@ -43,31 +44,6 @@ export function cmdOrCtrlPressed(e) {
return (isMac() && e.metaKey) || (!isMac() && e.ctrlKey);
}
-export function isChrome() {
- if (navigator.userAgent.indexOf('Chrome') > -1) {
- return true;
- }
- return false;
-}
-
-export function isSafari() {
- if (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) {
- return true;
- }
- return false;
-}
-
-export function isIosChrome() {
- // https://developer.chrome.com/multidevice/user-agent
- return navigator.userAgent.indexOf('CriOS') !== -1;
-}
-
-export function isMobileApp() {
- const userAgent = navigator.userAgent;
-
- return userAgent.indexOf('iPhone') !== -1 && userAgent.indexOf('Safari') === -1 && userAgent.indexOf('CriOS') === -1;
-}
-
export function isInRole(roles, inRole) {
var parts = roles.split(' ');
for (var i = 0; i < parts.length; i++) {
@@ -146,7 +122,7 @@ export function notifyMe(title, body, channel, teamId) {
var canDing = true;
export function ding() {
- if (!isBrowserFirefox() && canDing) {
+ if (!UserAgent.isFirefox() && canDing) {
var audio = new Audio(bing);
audio.play();
canDing = false;
@@ -751,7 +727,7 @@ export function updateCodeTheme(userTheme) {
xmlHTTP.open('GET', cssPath, true);
xmlHTTP.onload = function onLoad() {
$link.attr('href', cssPath);
- if (isBrowserFirefox()) {
+ if (UserAgent.isFirefox()) {
$link.one('load', () => {
changeCss('code.hljs', 'visibility: visible');
});
@@ -1048,25 +1024,6 @@ export function generateId() {
return id;
}
-export function isBrowserFirefox() {
- return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
-}
-
-// Checks if browser is IE10 or IE11
-export function isBrowserIE() {
- if (window.navigator && window.navigator.userAgent) {
- var ua = window.navigator.userAgent;
-
- return ua.indexOf('Trident/7.0') > 0 || ua.indexOf('Trident/6.0') > 0;
- }
-
- return false;
-}
-
-export function isBrowserEdge() {
- return window.navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1;
-}
-
export function getDirectChannelName(id, otherId) {
let handle;
@@ -1244,7 +1201,7 @@ export function fillArray(value, length) {
// Checks if a data transfer contains files not text, folders, etc..
// Slightly modified from http://stackoverflow.com/questions/6848043/how-do-i-detect-a-file-is-being-dragged-rather-than-a-draggable-element-on-my-pa
export function isFileTransfer(files) {
- if (isBrowserIE() || isBrowserEdge()) {
+ if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) {
return files.types != null && files.types.contains('Files');
}