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
Этот коммит содержится в:
коммит произвёл
Corey Hulen
родитель
790dd91e7d
Коммит
1de3bd3b43
@@ -8,6 +8,7 @@ import Client from 'client/web_client.jsx';
|
|||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import DelayedAction from 'utils/delayed_action.jsx';
|
import DelayedAction from 'utils/delayed_action.jsx';
|
||||||
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages} from 'react-intl';
|
import {intlShape, injectIntl, defineMessages} from 'react-intl';
|
||||||
@@ -311,13 +312,13 @@ class FileUpload extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let multiple = true;
|
let multiple = true;
|
||||||
if (Utils.isMobileApp()) {
|
if (UserAgent.isMobileApp()) {
|
||||||
// iOS WebViews don't upload videos properly in multiple mode
|
// iOS WebViews don't upload videos properly in multiple mode
|
||||||
multiple = false;
|
multiple = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let accept = '';
|
let accept = '';
|
||||||
if (Utils.isIosChrome()) {
|
if (UserAgent.isIosChrome()) {
|
||||||
// iOS Chrome can't upload videos at all
|
// iOS Chrome can't upload videos at all
|
||||||
accept = 'image/*';
|
accept = 'image/*';
|
||||||
}
|
}
|
||||||
|
|||||||
75
webapp/components/get_android_app/get_android_app.jsx
Обычный файл
75
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
webapp/components/get_ios_app/get_ios_app.jsx
Обычный файл
68
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
@@ -53,9 +54,11 @@ class NewChannelModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (Utils.isBrowserIE()) {
|
// ???
|
||||||
|
if (UserAgent.isInternetExplorer()) {
|
||||||
$('body').addClass('browser--ie');
|
$('body').addClass('browser--ie');
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as GlobalActions from 'actions/global_actions.jsx';
|
|||||||
|
|
||||||
import {createChannelIntroMessage} from 'utils/channel_intro_messages.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 Utils from 'utils/utils.jsx';
|
||||||
import * as PostUtils from 'utils/post_utils.jsx';
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
import DelayedAction from 'utils/delayed_action.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
|
// Temporary fix to solve ie11 rendering issue
|
||||||
let newSeparatorId = '';
|
let newSeparatorId = '';
|
||||||
if (!Utils.isBrowserIE()) {
|
if (!UserAgent.isInternetExplorer()) {
|
||||||
newSeparatorId = 'new_message_' + post.id;
|
newSeparatorId = 'new_message_' + post.id;
|
||||||
}
|
}
|
||||||
postCtls.push(
|
postCtls.push(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import TeamStore from 'stores/team_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 * as Utils from 'utils/utils.jsx';
|
||||||
import ErrorBar from 'components/error_bar.jsx';
|
import ErrorBar from 'components/error_bar.jsx';
|
||||||
import LoadingScreen from 'components/loading_screen.jsx';
|
import LoadingScreen from 'components/loading_screen.jsx';
|
||||||
@@ -176,7 +177,7 @@ export default class SelectTeam extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let teamSignUp;
|
let teamSignUp;
|
||||||
if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp())) {
|
if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !UserAgent.isMobileApp())) {
|
||||||
teamSignUp = (
|
teamSignUp = (
|
||||||
<div className='margin--extra'>
|
<div className='margin--extra'>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export default class SettingsSidebar extends React.Component {
|
|||||||
$(e.target).closest('.settings-modal').addClass('display--content');
|
$(e.target).closest('.settings-modal').addClass('display--content');
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (Utils.isBrowserFirefox()) {
|
if (UserAgent.isFirefox()) {
|
||||||
$('.settings-modal .settings-table .nav').addClass('position--top');
|
$('.settings-modal .settings-table .nav').addClass('position--top');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import TeamStore from 'stores/team_store.jsx';
|
|||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
|
|
||||||
import * as GlobalActions from 'actions/global_actions.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 * as Utils from 'utils/utils.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
@@ -296,7 +297,7 @@ export default class SidebarRightMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nativeAppLink = null;
|
let nativeAppLink = null;
|
||||||
if (global.window.mm_config.AppDownloadLink && !Utils.isMobileApp()) {
|
if (global.window.mm_config.AppDownloadLink && !UserAgent.isMobileApp()) {
|
||||||
nativeAppLink = (
|
nativeAppLink = (
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import UserStore from 'stores/user_store.jsx';
|
|||||||
|
|
||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
import * as AsyncClient from 'utils/async_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 * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
function getNotificationsStateFromStores() {
|
function getNotificationsStateFromStores() {
|
||||||
var user = UserStore.getCurrentUser();
|
var user = UserStore.getCurrentUser();
|
||||||
var soundNeeded = !Utils.isBrowserFirefox();
|
var soundNeeded = !UserAgent.isFirefox();
|
||||||
|
|
||||||
var sound = 'true';
|
var sound = 'true';
|
||||||
if (user.notify_props && user.notify_props.desktop_sound) {
|
if (user.notify_props && user.notify_props.desktop_sound) {
|
||||||
|
|||||||
@@ -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.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.title": "General Settings",
|
||||||
"general_tab.yes": "Yes",
|
"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.clipboard": " Link copied to clipboard.",
|
||||||
"get_link.close": "Close",
|
"get_link.close": "Close",
|
||||||
"get_link.copy": "Copy Link",
|
"get_link.copy": "Copy Link",
|
||||||
|
|||||||
Двоичные данные
webapp/images/app-store-button.png
Обычный файл
Двоичные данные
webapp/images/app-store-button.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 11 KiB |
Двоичные данные
webapp/images/iphone-6-mockup.png
Обычный файл
Двоичные данные
webapp/images/iphone-6-mockup.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 76 KiB |
Двоичные данные
webapp/images/nexus-6p-mockup.png
Обычный файл
Двоичные данные
webapp/images/nexus-6p-mockup.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 59 KiB |
@@ -9,7 +9,23 @@ import claimAccountRoute from 'routes/route_claim.jsx';
|
|||||||
import createTeamRoute from 'routes/route_create_team.jsx';
|
import createTeamRoute from 'routes/route_create_team.jsx';
|
||||||
import teamRoute from 'routes/route_team.jsx';
|
import teamRoute from 'routes/route_team.jsx';
|
||||||
|
|
||||||
|
import BrowserStore from 'stores/browser_store.jsx';
|
||||||
import ErrorStore from 'stores/error_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) {
|
function preLoggedIn(nextState, replace, callback) {
|
||||||
ErrorStore.clearLastError();
|
ErrorStore.clearLastError();
|
||||||
callback();
|
callback();
|
||||||
@@ -28,6 +44,7 @@ export default {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
|
onEnter: preLogin,
|
||||||
getComponents: (location, callback) => {
|
getComponents: (location, callback) => {
|
||||||
System.import('components/login/login_controller.jsx').then(RouteUtils.importComponentSuccess(callback));
|
System.import('components/login/login_controller.jsx').then(RouteUtils.importComponentSuccess(callback));
|
||||||
}
|
}
|
||||||
@@ -66,6 +83,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',
|
path: 'error',
|
||||||
getComponents: (location, callback) => {
|
getComponents: (location, callback) => {
|
||||||
|
|||||||
96
webapp/sass/routes/_get-app.scss
Обычный файл
96
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
@import 'compliance';
|
@import 'compliance';
|
||||||
@import 'docs';
|
@import 'docs';
|
||||||
@import 'error-page';
|
@import 'error-page';
|
||||||
|
@import 'get-app';
|
||||||
@import 'loading';
|
@import 'loading';
|
||||||
@import 'print';
|
@import 'print';
|
||||||
@import 'settings';
|
@import 'settings';
|
||||||
|
|||||||
@@ -20,27 +20,6 @@ function getPrefix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BrowserStoreClass {
|
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) {
|
setItem(name, value) {
|
||||||
this.setGlobalItem(getPrefix() + name, value);
|
this.setGlobalItem(getPrefix() + name, value);
|
||||||
}
|
}
|
||||||
@@ -162,9 +141,10 @@ class BrowserStoreClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
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 logoutId = sessionStorage.getItem('__logout__');
|
||||||
const serverVersion = this.getLastServerVersion();
|
const serverVersion = this.getLastServerVersion();
|
||||||
|
const landingPageSeen = this.hasSeenLandingPage();
|
||||||
|
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
@@ -176,11 +156,10 @@ class BrowserStoreClass {
|
|||||||
if (serverVersion) {
|
if (serverVersion) {
|
||||||
this.setLastServerVersion(serverVersion);
|
this.setLastServerVersion(serverVersion);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
clearAll() {
|
if (landingPageSeen) {
|
||||||
sessionStorage.clear();
|
this.setLandingPageSeen(landingPageSeen);
|
||||||
localStorage.clear();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocalStorageSupported() {
|
isLocalStorageSupported() {
|
||||||
@@ -210,6 +189,14 @@ class BrowserStoreClass {
|
|||||||
|
|
||||||
return this.checkedLocalStorageSupported;
|
return this.checkedLocalStorageSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasSeenLandingPage() {
|
||||||
|
return JSON.parse(sessionStorage.getItem('__landingPageSeen__'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setLandingPageSeen(landingPageSeen) {
|
||||||
|
return sessionStorage.setItem('__landingPageSeen__', JSON.stringify(landingPageSeen));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var BrowserStore = new BrowserStoreClass();
|
var BrowserStore = new BrowserStoreClass();
|
||||||
|
|||||||
86
webapp/utils/user_agent.jsx
Обычный файл
86
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;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import Constants from 'utils/constants.jsx';
|
|||||||
var ActionTypes = Constants.ActionTypes;
|
var ActionTypes = Constants.ActionTypes;
|
||||||
import * as AsyncClient from './async_client.jsx';
|
import * as AsyncClient from './async_client.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
|
|
||||||
import {browserHistory} from 'react-router/es6';
|
import {browserHistory} from 'react-router/es6';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
@@ -43,31 +44,6 @@ export function cmdOrCtrlPressed(e) {
|
|||||||
return (isMac() && e.metaKey) || (!isMac() && e.ctrlKey);
|
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) {
|
export function isInRole(roles, inRole) {
|
||||||
var parts = roles.split(' ');
|
var parts = roles.split(' ');
|
||||||
for (var i = 0; i < parts.length; i++) {
|
for (var i = 0; i < parts.length; i++) {
|
||||||
@@ -146,7 +122,7 @@ export function notifyMe(title, body, channel, teamId) {
|
|||||||
var canDing = true;
|
var canDing = true;
|
||||||
|
|
||||||
export function ding() {
|
export function ding() {
|
||||||
if (!isBrowserFirefox() && canDing) {
|
if (!UserAgent.isFirefox() && canDing) {
|
||||||
var audio = new Audio(bing);
|
var audio = new Audio(bing);
|
||||||
audio.play();
|
audio.play();
|
||||||
canDing = false;
|
canDing = false;
|
||||||
@@ -751,7 +727,7 @@ export function updateCodeTheme(userTheme) {
|
|||||||
xmlHTTP.open('GET', cssPath, true);
|
xmlHTTP.open('GET', cssPath, true);
|
||||||
xmlHTTP.onload = function onLoad() {
|
xmlHTTP.onload = function onLoad() {
|
||||||
$link.attr('href', cssPath);
|
$link.attr('href', cssPath);
|
||||||
if (isBrowserFirefox()) {
|
if (UserAgent.isFirefox()) {
|
||||||
$link.one('load', () => {
|
$link.one('load', () => {
|
||||||
changeCss('code.hljs', 'visibility: visible');
|
changeCss('code.hljs', 'visibility: visible');
|
||||||
});
|
});
|
||||||
@@ -1048,25 +1024,6 @@ export function generateId() {
|
|||||||
return id;
|
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) {
|
export function getDirectChannelName(id, otherId) {
|
||||||
let handle;
|
let handle;
|
||||||
|
|
||||||
@@ -1244,7 +1201,7 @@ export function fillArray(value, length) {
|
|||||||
// Checks if a data transfer contains files not text, folders, etc..
|
// 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
|
// 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) {
|
export function isFileTransfer(files) {
|
||||||
if (isBrowserIE() || isBrowserEdge()) {
|
if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) {
|
||||||
return files.types != null && files.types.contains('Files');
|
return files.types != null && files.types.contains('Files');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user