summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
Diffstat (limited to 'webapp')
-rw-r--r--webapp/actions/global_actions.jsx4
-rw-r--r--webapp/actions/websocket_actions.jsx148
-rw-r--r--webapp/components/admin_console/webhook_settings.jsx6
-rw-r--r--webapp/components/post_view/components/post_list.jsx19
-rw-r--r--webapp/components/suggestion/switch_channel_provider.jsx8
-rw-r--r--webapp/components/team_members_dropdown.jsx2
-rw-r--r--webapp/i18n/en.json6
-rw-r--r--webapp/package.json2
-rw-r--r--webapp/sass/layout/_headers.scss4
-rw-r--r--webapp/utils/channel_intro_messages.jsx33
-rw-r--r--webapp/utils/websocket_client.jsx7
-rw-r--r--webapp/webpack.config.js9
12 files changed, 102 insertions, 146 deletions
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx
index d9b89f987..8d90b226d 100644
--- a/webapp/actions/global_actions.jsx
+++ b/webapp/actions/global_actions.jsx
@@ -12,7 +12,6 @@ import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
-import * as Websockets from 'actions/websocket_actions.jsx';
import {handleNewPost} from 'actions/post_actions.jsx';
import Constants from 'utils/constants.jsx';
@@ -20,6 +19,7 @@ const ActionTypes = Constants.ActionTypes;
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
+import WebSocketClient from 'utils/websocket_client.jsx';
import * as Utils from 'utils/utils.jsx';
import en from 'i18n/en.json';
@@ -439,7 +439,7 @@ var lastTimeTypingSent = 0;
export function emitLocalUserTypingEvent(channelId, parentId) {
const t = Date.now();
if ((t - lastTimeTypingSent) > Constants.UPDATE_TYPING_MS) {
- Websockets.sendMessage({channel_id: channelId, action: 'typing', props: {parent_id: parentId}, state: {}});
+ WebSocketClient.userTyping(channelId, parentId);
lastTimeTypingSent = t;
}
}
diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx
index 7be9d84f3..e6997b9cc 100644
--- a/webapp/actions/websocket_actions.jsx
+++ b/webapp/actions/websocket_actions.jsx
@@ -11,6 +11,7 @@ import ErrorStore from 'stores/error_store.jsx';
import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars
import Client from 'utils/web_client.jsx';
+import WebSocketClient from 'utils/websocket_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
@@ -23,16 +24,9 @@ const SocketEvents = Constants.SocketEvents;
import {browserHistory} from 'react-router/es6';
const MAX_WEBSOCKET_FAILS = 7;
-const MIN_WEBSOCKET_RETRY_TIME = 3000; // 3 sec
-const MAX_WEBSOCKET_RETRY_TIME = 300000; // 5 mins
-
-var conn = null;
-var connectFailCount = 0;
-var pastFirstInit = false;
-var manuallyClosed = false;
export function initialize() {
- if (window.WebSocket && !conn) {
+ if (window.WebSocket) {
let protocol = 'ws://';
if (window.location.protocol === 'https:') {
protocol = 'wss://';
@@ -40,85 +34,35 @@ export function initialize() {
const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + Client.getUsersRoute() + '/websocket';
- if (connectFailCount === 0) {
- console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
- }
-
- manuallyClosed = false;
-
- conn = new WebSocket(connUrl);
-
- conn.onopen = () => {
- if (connectFailCount > 0) {
- console.log('websocket re-established connection'); //eslint-disable-line no-console
- AsyncClient.getChannels();
- AsyncClient.getPosts(ChannelStore.getCurrentId());
- }
-
- if (pastFirstInit) {
- ErrorStore.clearLastError();
- ErrorStore.emitChange();
- }
-
- pastFirstInit = true;
- connectFailCount = 0;
- };
-
- conn.onclose = () => {
- conn = null;
-
- if (connectFailCount === 0) {
- console.log('websocket closed'); //eslint-disable-line no-console
- }
-
- if (manuallyClosed) {
- return;
- }
-
- connectFailCount = connectFailCount + 1;
-
- var retryTime = MIN_WEBSOCKET_RETRY_TIME;
-
- if (connectFailCount > MAX_WEBSOCKET_FAILS) {
- ErrorStore.storeLastError({message: Utils.localizeMessage('channel_loader.socketError', 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.')});
-
- // If we've failed a bunch of connections then start backing off
- retryTime = MIN_WEBSOCKET_RETRY_TIME * connectFailCount * connectFailCount;
- if (retryTime > MAX_WEBSOCKET_RETRY_TIME) {
- retryTime = MAX_WEBSOCKET_RETRY_TIME;
- }
- }
-
- ErrorStore.setConnectionErrorCount(connectFailCount);
- ErrorStore.emitChange();
-
- setTimeout(
- () => {
- initialize();
- },
- retryTime
- );
- };
-
- conn.onerror = (evt) => {
- if (connectFailCount <= 1) {
- console.log('websocket error'); //eslint-disable-line no-console
- console.log(evt); //eslint-disable-line no-console
- }
- };
-
- conn.onmessage = (evt) => {
- const msg = JSON.parse(evt.data);
- handleMessage(msg);
- };
+ WebSocketClient.initialize(connUrl);
+ WebSocketClient.setEventCallback(handleEvent);
+ WebSocketClient.setReconnectCallback(handleReconnect);
+ WebSocketClient.setCloseCallback(handleClose);
}
}
-function handleMessage(msg) {
- // Let the store know we are online. This probably shouldn't be here.
- UserStore.setStatus(msg.user_id, 'online');
+export function close() {
+ WebSocketClient.close();
+}
+
+function handleReconnect() {
+ AsyncClient.getChannels();
+ AsyncClient.getPosts(ChannelStore.getCurrentId());
+ ErrorStore.clearLastError();
+ ErrorStore.emitChange();
+}
+
+function handleClose(failCount) {
+ if (failCount > MAX_WEBSOCKET_FAILS) {
+ ErrorStore.storeLastError({message: Utils.localizeMessage('channel_loader.socketError', 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.')});
+ }
+
+ ErrorStore.setConnectionErrorCount(failCount);
+ ErrorStore.emitChange();
+}
- switch (msg.action) {
+function handleEvent(msg) {
+ switch (msg.event) {
case SocketEvents.POSTED:
case SocketEvents.EPHEMERAL_MESSAGE:
handleNewPostEvent(msg);
@@ -172,36 +116,14 @@ function handleMessage(msg) {
}
}
-export function sendMessage(msg) {
- if (conn && conn.readyState === WebSocket.OPEN) {
- var teamId = TeamStore.getCurrentId();
- if (teamId && teamId.length > 0) {
- msg.team_id = teamId;
- }
-
- conn.send(JSON.stringify(msg));
- } else if (!conn || conn.readyState === WebSocket.Closed) {
- conn = null;
- initialize();
- }
-}
-
-export function close() {
- manuallyClosed = true;
- connectFailCount = 0;
- if (conn && conn.readyState === WebSocket.OPEN) {
- conn.close();
- }
-}
-
function handleNewPostEvent(msg) {
- const post = JSON.parse(msg.props.post);
+ const post = JSON.parse(msg.data.post);
handleNewPost(post, msg);
}
function handlePostEditEvent(msg) {
// Store post
- const post = JSON.parse(msg.props.post);
+ const post = JSON.parse(msg.data.post);
PostStore.storePost(post);
PostStore.emitChange();
@@ -214,7 +136,7 @@ function handlePostEditEvent(msg) {
}
function handlePostDeleteEvent(msg) {
- const post = JSON.parse(msg.props.post);
+ const post = JSON.parse(msg.data.post);
GlobalActions.emitPostDeletedEvent(post);
}
@@ -257,12 +179,12 @@ function handleUserRemovedEvent(msg) {
if (UserStore.getCurrentId() === msg.user_id) {
AsyncClient.getChannels();
- if (msg.props.remover_id !== msg.user_id &&
+ if (msg.data.remover_id !== msg.user_id &&
msg.channel_id === ChannelStore.getCurrentId() &&
$('#removed_from_channel').length > 0) {
var sentState = {};
sentState.channelName = ChannelStore.getCurrent().display_name;
- sentState.remover = UserStore.getProfile(msg.props.remover_id).username;
+ sentState.remover = UserStore.getProfile(msg.data.remover_id).username;
BrowserStore.setItem('channel-removed-state', sentState);
$('#removed_from_channel').modal('show');
@@ -290,12 +212,10 @@ function handleChannelDeletedEvent(msg) {
}
function handlePreferenceChangedEvent(msg) {
- const preference = JSON.parse(msg.props.preference);
+ const preference = JSON.parse(msg.data.preference);
GlobalActions.emitPreferenceChangedEvent(preference);
}
function handleUserTypingEvent(msg) {
- if (TeamStore.getCurrentId() === msg.team_id) {
- GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id);
- }
+ GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.data.parent_id);
}
diff --git a/webapp/components/admin_console/webhook_settings.jsx b/webapp/components/admin_console/webhook_settings.jsx
index 18a3ed7ad..3c8ea5466 100644
--- a/webapp/components/admin_console/webhook_settings.jsx
+++ b/webapp/components/admin_console/webhook_settings.jsx
@@ -64,7 +64,7 @@ export default class WebhookSettings extends AdminSettings {
helpText={
<FormattedMessage
id='admin.service.webhooksDescription'
- defaultMessage='When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'
+ defaultMessage='When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag. See <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableIncomingWebhooks}
@@ -81,7 +81,7 @@ export default class WebhookSettings extends AdminSettings {
helpText={
<FormattedMessage
id='admin.service.outWebhooksDesc'
- defaultMessage='When true, outgoing webhooks will be allowed.'
+ defaultMessage='When true, outgoing webhooks will be allowed. See <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableOutgoingWebhooks}
@@ -98,7 +98,7 @@ export default class WebhookSettings extends AdminSettings {
helpText={
<FormattedMessage
id='admin.service.cmdsDesc'
- defaultMessage='When true, user created slash commands will be allowed.'
+ defaultMessage='When true, custom slash commands will be allowed. See <a href="http://docs.mattermost.com/developer/slash-commands.html" target="_blank">documentation</a> to learn more.'
/>
}
value={this.state.enableCommands}
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index 17e29da2e..690cd96c7 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -18,11 +18,15 @@ import DelayedAction from 'utils/delayed_action.jsx';
import Constants from 'utils/constants.jsx';
const ScrollTypes = Constants.ScrollTypes;
+import PreferenceStore from 'stores/preference_store.jsx';
+
import {FormattedDate, FormattedMessage} from 'react-intl';
import React from 'react';
import ReactDOM from 'react-dom';
+const Preferences = Constants.Preferences;
+
export default class PostList extends React.Component {
constructor(props) {
super(props);
@@ -44,16 +48,17 @@ export default class PostList extends React.Component {
this.scrollStopAction = new DelayedAction(this.handleScrollStop);
- if (props.channel) {
- this.introText = createChannelIntroMessage(props.channel);
- } else {
- this.introText = this.getArchivesIntroMessage();
- }
-
this.state = {
isScrolling: false,
+ fullWidthIntro: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
topPostId: null
};
+
+ if (props.channel) {
+ this.introText = createChannelIntroMessage(props.channel, this.state.fullWidthIntro);
+ } else {
+ this.introText = this.getArchivesIntroMessage();
+ }
}
isAtBottom() {
@@ -395,7 +400,7 @@ export default class PostList extends React.Component {
getArchivesIntroMessage() {
return (
- <div className='channel-intro'>
+ <div className={'channel-intro'}>
<h4 className='channel-intro__title'>
<FormattedMessage
id='post_focus_view.beginning'
diff --git a/webapp/components/suggestion/switch_channel_provider.jsx b/webapp/components/suggestion/switch_channel_provider.jsx
index c12918c51..e092d9b5c 100644
--- a/webapp/components/suggestion/switch_channel_provider.jsx
+++ b/webapp/components/suggestion/switch_channel_provider.jsx
@@ -57,7 +57,13 @@ export default class SwitchChannelProvider {
}
}
- channels.sort((a, b) => a.display_name.localeCompare(b.display_name));
+ channels.sort((a, b) => {
+ if (a.display_name === b.display_name) {
+ return a.name.localeCompare(b.name);
+ }
+ return a.display_name.localeCompare(b.display_name);
+ });
+
const channelNames = channels.map((channel) => channel.name);
SuggestionStore.addSuggestions(suggestionId, channelNames, channels, SwitchChannelSuggestion, channelPrefix);
diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown.jsx
index 43449635d..f8c217aed 100644
--- a/webapp/components/team_members_dropdown.jsx
+++ b/webapp/components/team_members_dropdown.jsx
@@ -186,7 +186,7 @@ export default class TeamMembersDropdown extends React.Component {
}
const me = UserStore.getCurrentUser();
- let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin';
+ let showMakeMember = teamMember.roles === 'admin' && user.roles !== 'system_admin';
let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin';
let showMakeActive = false;
let showMakeNotActive = user.roles !== 'system_admin';
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 0d3080d03..f000dbf55 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -521,7 +521,7 @@
"admin.service.attemptDescription": "Number of login attempts allowed before a user is locked out and required to reset their password via email.",
"admin.service.attemptExample": "Ex \"10\"",
"admin.service.attemptTitle": "Maximum Login Attempts:",
- "admin.service.cmdsDesc": "When true, user created slash commands will be allowed.",
+ "admin.service.cmdsDesc": "When true, custom slash commands will be allowed. See <a href='http://docs.mattermost.com/developer/slash-commands.html' target='_blank'>documentation</a> to learn more.",
"admin.service.cmdsTitle": "Enable Custom Slash Commands: ",
"admin.service.corsDescription": "Enable HTTP Cross origin request from a specific domain. Use \"*\" if you want to allow CORS from any domain or leave it blank to disable it.",
"admin.service.corsEx": "http://example.com",
@@ -544,7 +544,7 @@
"admin.service.mfaTitle": "Enable Multi-factor Authentication:",
"admin.service.mobileSessionDays": "Session length for mobile apps (days):",
"admin.service.mobileSessionDaysDesc": "Mobile sessions will expire after the number of days specified and will require users to sign in again.",
- "admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed.",
+ "admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed. See <a href='http://docs.mattermost.com/developer/webhooks-outgoing.html' target='_blank'>documentation</a> to learn more.",
"admin.service.outWebhooksTitle": "Enable Outgoing Webhooks: ",
"admin.service.overrideDescription": "When true, webhooks and slash commands will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.",
"admin.service.overrideTitle": "Enable webhooks and slash commands to override usernames:",
@@ -562,7 +562,7 @@
"admin.service.testingTitle": "Enable Testing Commands: ",
"admin.service.webSessionDays": "Session length for email and LDAP authentication (days):",
"admin.service.webSessionDaysDesc": "Email or LDAP sessions will expire after the number of days specified and will require users to sign in again.",
- "admin.service.webhooksDescription": "When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.",
+ "admin.service.webhooksDescription": "When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag. See <a href='http://docs.mattermost.com/developer/webhooks-incoming.html' target='_blank'>documentation</a> to learn more.",
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
"admin.sidebar.advanced": "Advanced",
diff --git a/webapp/package.json b/webapp/package.json
index 3db9d0794..984affd08 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -18,7 +18,7 @@
"keymirror": "0.1.1",
"marked": "mattermost/marked#12d2be4cdf54d4ec95fead934e18840b6a2c1a7b",
"match-at": "0.1.0",
- "mattermost": "mattermost/mattermost-javascript#5815f14f0d1960aa4c99797b09d949d2959eb24f",
+ "mattermost": "mattermost/mattermost-javascript#4cdaeba22ff82bf93dc417af1ab4e89e3248d624",
"object-assign": "4.1.0",
"perfect-scrollbar": "0.6.11",
"react": "15.0.2",
diff --git a/webapp/sass/layout/_headers.scss b/webapp/sass/layout/_headers.scss
index 56f7cd6e8..1e7046340 100644
--- a/webapp/sass/layout/_headers.scss
+++ b/webapp/sass/layout/_headers.scss
@@ -162,6 +162,10 @@
margin: 0 auto 15px;
padding: 0 15px;
+ &.channel-intro--centered {
+ max-width: 1020px;
+ }
+
.intro-links {
display: inline-block;
margin: 0 1.5em 10px 0;
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index 50d12ed42..9a232dbc0 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -16,20 +16,25 @@ import Client from 'utils/web_client.jsx';
import React from 'react';
import {FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
-export function createChannelIntroMessage(channel) {
+export function createChannelIntroMessage(channel, fullWidthIntro) {
+ let centeredIntro = '';
+ if (!fullWidthIntro) {
+ centeredIntro = 'channel-intro--centered';
+ }
+
if (channel.type === 'D') {
- return createDMIntroMessage(channel);
+ return createDMIntroMessage(channel, centeredIntro);
} else if (ChannelStore.isDefault(channel)) {
- return createDefaultIntroMessage(channel);
+ return createDefaultIntroMessage(channel, centeredIntro);
} else if (channel.name === Constants.OFFTOPIC_CHANNEL) {
- return createOffTopicIntroMessage(channel);
+ return createOffTopicIntroMessage(channel, centeredIntro);
} else if (channel.type === 'O' || channel.type === 'P') {
- return createStandardIntroMessage(channel);
+ return createStandardIntroMessage(channel, centeredIntro);
}
return null;
}
-export function createDMIntroMessage(channel) {
+export function createDMIntroMessage(channel, centeredIntro) {
var teammate = Utils.getDirectTeammate(channel.id);
if (teammate) {
@@ -39,7 +44,7 @@ export function createDMIntroMessage(channel) {
}
return (
- <div className='channel-intro'>
+ <div className={'channel-intro ' + centeredIntro}>
<div className='post-profile-img__container channel-intro-img'>
<img
className='post-profile-img'
@@ -68,7 +73,7 @@ export function createDMIntroMessage(channel) {
}
return (
- <div className='channel-intro'>
+ <div className={'channel-intro ' + centeredIntro}>
<p className='channel-intro-text'>
<FormattedMessage
id='intro_messages.teammate'
@@ -79,9 +84,9 @@ export function createDMIntroMessage(channel) {
);
}
-export function createOffTopicIntroMessage(channel) {
+export function createOffTopicIntroMessage(channel, centeredIntro) {
return (
- <div className='channel-intro'>
+ <div className={'channel-intro ' + centeredIntro}>
<FormattedHTMLMessage
id='intro_messages.offTopic'
defaultMessage='<h4 class="channel-intro__title">Beginning of {display_name}</h4><p class="channel-intro__content">This is the start of {display_name}, a channel for non-work-related conversations.<br/></p>'
@@ -95,7 +100,7 @@ export function createOffTopicIntroMessage(channel) {
);
}
-export function createDefaultIntroMessage(channel) {
+export function createDefaultIntroMessage(channel, centeredIntro) {
let inviteModalLink = (
<a
className='intro-links'
@@ -122,7 +127,7 @@ export function createDefaultIntroMessage(channel) {
}
return (
- <div className='channel-intro'>
+ <div className={'channel-intro ' + centeredIntro}>
<FormattedHTMLMessage
id='intro_messages.default'
defaultMessage="<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>"
@@ -137,7 +142,7 @@ export function createDefaultIntroMessage(channel) {
);
}
-export function createStandardIntroMessage(channel) {
+export function createStandardIntroMessage(channel, centeredIntro) {
var uiName = channel.display_name;
var creatorName = '';
@@ -211,7 +216,7 @@ export function createStandardIntroMessage(channel) {
}
return (
- <div className='channel-intro'>
+ <div className={'channel-intro ' + centeredIntro}>
<h4 className='channel-intro__title'>
<FormattedMessage
id='intro_messages.beginning'
diff --git a/webapp/utils/websocket_client.jsx b/webapp/utils/websocket_client.jsx
new file mode 100644
index 000000000..135d96466
--- /dev/null
+++ b/webapp/utils/websocket_client.jsx
@@ -0,0 +1,7 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import WebSocketClient from 'mattermost/websocket_client.jsx';
+
+var WebClient = new WebSocketClient();
+export default WebClient;
diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js
index 2911c0c7d..88635ef03 100644
--- a/webapp/webpack.config.js
+++ b/webapp/webpack.config.js
@@ -53,6 +53,15 @@ var config = {
}
},
{
+ test: /node_modules\/mattermost\/websocket_client\.jsx?$/,
+ loader: 'babel',
+ query: {
+ presets: ['react', 'es2015-webpack', 'stage-0'],
+ plugins: ['transform-runtime'],
+ cacheDirectory: DEV
+ }
+ },
+ {
test: /\.json$/,
loader: 'json'
},