summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
Diffstat (limited to 'webapp')
-rw-r--r--webapp/.eslintrc.json26
-rw-r--r--webapp/actions/global_actions.jsx7
-rw-r--r--webapp/actions/team_actions.jsx18
-rw-r--r--webapp/actions/user_actions.jsx18
-rw-r--r--webapp/actions/websocket_actions.jsx2
-rw-r--r--webapp/client/client.jsx27
-rw-r--r--webapp/client/web_client.jsx10
-rw-r--r--webapp/client/webrtc_session.jsx6
-rw-r--r--webapp/components/admin_console/admin_sidebar.jsx16
-rw-r--r--webapp/components/admin_console/cluster_settings.jsx4
-rw-r--r--webapp/components/admin_console/cluster_table.jsx2
-rw-r--r--webapp/components/admin_console/cluster_table_container.jsx6
-rw-r--r--webapp/components/admin_console/file_upload_setting.jsx2
-rw-r--r--webapp/components/admin_console/logs.jsx11
-rw-r--r--webapp/components/admin_console/metrics_settings.jsx96
-rw-r--r--webapp/components/admin_console/users_and_teams_settings.jsx22
-rw-r--r--webapp/components/admin_console/webrtc_settings.jsx36
-rw-r--r--webapp/components/change_url_modal.jsx2
-rw-r--r--webapp/components/channel_header.jsx49
-rw-r--r--webapp/components/code_preview.jsx6
-rw-r--r--webapp/components/create_team/components/display_name.jsx18
-rw-r--r--webapp/components/create_team/components/team_url.jsx42
-rw-r--r--webapp/components/integrations/components/edit_command.jsx731
-rw-r--r--webapp/components/integrations/components/installed_command.jsx9
-rw-r--r--webapp/components/logged_in.jsx16
-rw-r--r--webapp/components/login/login_controller.jsx9
-rw-r--r--webapp/components/more_direct_channels.jsx4
-rw-r--r--webapp/components/navbar.jsx8
-rw-r--r--webapp/components/new_channel_flow.jsx14
-rw-r--r--webapp/components/new_channel_modal.jsx3
-rw-r--r--webapp/components/password_reset_send_link.jsx6
-rw-r--r--webapp/components/search_results.jsx2
-rw-r--r--webapp/components/search_results_item.jsx215
-rw-r--r--webapp/components/sidebar_right_menu.jsx1
-rw-r--r--webapp/components/signup/components/signup_email.jsx6
-rw-r--r--webapp/components/signup/components/signup_ldap.jsx6
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx23
-rw-r--r--webapp/components/suggestion/suggestion_box.jsx3
-rw-r--r--webapp/components/team_general_tab.jsx21
-rw-r--r--webapp/components/user_profile.jsx43
-rw-r--r--webapp/components/user_settings/manage_languages.jsx5
-rw-r--r--webapp/components/user_settings/user_settings_general.jsx7
-rw-r--r--webapp/components/user_settings/user_settings_modal.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx25
-rw-r--r--webapp/components/youtube_video.jsx2
-rw-r--r--webapp/i18n/en.json67
-rw-r--r--webapp/i18n/i18n.jsx20
-rw-r--r--webapp/package.json70
-rw-r--r--webapp/routes/route_admin_console.jsx5
-rw-r--r--webapp/routes/route_integrations.jsx6
-rw-r--r--webapp/sass/components/_emoticons.scss9
-rw-r--r--webapp/sass/components/_inputs.scss7
-rw-r--r--webapp/sass/components/_scrollbar.scss16
-rw-r--r--webapp/sass/components/_tooltip.scss6
-rw-r--r--webapp/sass/components/_webrtc.scss11
-rw-r--r--webapp/sass/responsive/_mobile.scss160
-rw-r--r--webapp/sass/responsive/_tablet.scss4
-rw-r--r--webapp/sass/routes/_admin-console.scss6
-rw-r--r--webapp/sass/routes/_settings.scss4
-rw-r--r--webapp/sass/routes/_signup.scss2
-rw-r--r--webapp/sass/utils/_mixins.scss61
-rw-r--r--webapp/stores/browser_store.jsx6
-rw-r--r--webapp/stores/integration_store.jsx9
-rw-r--r--webapp/stores/notification_store.jsx22
-rw-r--r--webapp/stores/post_store.jsx6
-rw-r--r--webapp/stores/search_store.jsx19
-rw-r--r--webapp/stores/team_store.jsx4
-rw-r--r--webapp/tests/client_command.test.jsx28
-rw-r--r--webapp/tests/client_user.test.jsx15
-rw-r--r--webapp/utils/async_client.jsx23
-rw-r--r--webapp/utils/channel_intro_messages.jsx29
-rw-r--r--webapp/utils/constants.jsx15
-rw-r--r--webapp/utils/emoticons.jsx6
-rw-r--r--webapp/utils/markdown.jsx2
-rw-r--r--webapp/utils/text_formatting.jsx4
-rw-r--r--webapp/utils/utils.jsx318
-rw-r--r--webapp/webpack.config.js50
77 files changed, 1967 insertions, 630 deletions
diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json
index 2762483ef..183df2017 100644
--- a/webapp/.eslintrc.json
+++ b/webapp/.eslintrc.json
@@ -1,12 +1,13 @@
{
"extends": "eslint:recommended",
"parserOptions": {
- "ecmaVersion": 6,
+ "ecmaVersion": 8,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"impliedStrict": true,
- "modules": true
+ "modules": true,
+ "experimentalObjectRestSpread": true
}
},
"parser": "babel-eslint",
@@ -50,6 +51,7 @@
"dot-notation": 2,
"eqeqeq": [2, "smart"],
"func-call-spacing": [2, "never"],
+ "func-name-matching": 0,
"func-names": 2,
"func-style": [2, "declaration"],
"generator-star-spacing": [2, {"before": false, "after": true}],
@@ -136,6 +138,7 @@
"no-proto": 2,
"no-redeclare": 2,
"no-return-assign": [2, "always"],
+ "no-return-await": 2,
"no-script-url": 2,
"no-self-assign": [2, {"props": true}],
"no-self-compare": 2,
@@ -166,6 +169,7 @@
"no-useless-constructor": 2,
"no-useless-escape": 2,
"no-useless-rename": 2,
+ "no-useless-return": 2,
"no-var": 0,
"no-void": 2,
"no-warning-comments": 1,
@@ -182,7 +186,6 @@
"prefer-arrow-callback": 2,
"prefer-const": 2,
"prefer-numeric-literals": 2,
- "prefer-reflect": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 0,
@@ -190,6 +193,7 @@
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"react/display-name": [2, { "ignoreTranspilerName": false }],
+ "react/forbid-component-props": 0,
"react/jsx-boolean-value": [2, "always"],
"react/jsx-closing-bracket-location": [2, { "location": "tag-aligned" }],
"react/jsx-curly-spacing": [2, "never"],
@@ -202,26 +206,33 @@
"react/jsx-key": 2,
"react/jsx-max-props-per-line": [2, { "maximum": 1 }],
"react/jsx-no-bind": 0,
+ "react/jsx-no-comment-textnodes": 2,
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
"react/jsx-no-literals": 2,
"react/jsx-no-target-blank": 2,
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-space-before-closing": [2, "never"],
+ "react/jsx-tag-spacing": [2, { "closingSlash": "never", "beforeSelfClosing": "never", "afterOpening": "never" }],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
- "react/jsx-no-comment-textnodes": 2,
+ "react/jsx-wrap-multilines": 2,
+ "react/no-children-prop": 2,
"react/no-danger": 0,
+ "react/no-danger-with-children": 2,
"react/no-deprecated": 2,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
+ "react/no-find-dom-node": 1,
"react/no-is-mounted": 2,
"react/no-multi-comp": [2, { "ignoreStateless": true }],
"react/no-render-return-value": 2,
"react/no-set-state": 0,
"react/no-string-refs": 0,
+ "react/no-unescaped-entities": 2,
"react/no-unknown-property": 2,
+ "react/no-unused-prop-types": [1, {"skipShapeProps": true}],
"react/prefer-es6-class": 2,
"react/prefer-stateless-function": 0,
"react/prop-types": 2,
@@ -229,14 +240,7 @@
"react/require-render-return": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
- "react/jsx-wrap-multilines": 2,
- "react/no-find-dom-node": 1,
- "react/forbid-component-props": 0,
- "react/no-danger-with-children": 2,
- "react/no-unused-prop-types": [1, {"skipShapeProps": true}],
"react/style-prop-object": 2,
- "react/no-children-prop": 2,
- "react/no-unescaped-entities": 2,
"require-yield": 2,
"rest-spread-spacing": [2, "never"],
"semi": [2, "always"],
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx
index a70462295..9337595af 100644
--- a/webapp/actions/global_actions.jsx
+++ b/webapp/actions/global_actions.jsx
@@ -535,3 +535,10 @@ export function toggleSideBarAction(visible) {
});
}
}
+
+export function emitBrowserFocus(focus) {
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.BROWSER_CHANGE_FOCUS,
+ focus
+ });
+}
diff --git a/webapp/actions/team_actions.jsx b/webapp/actions/team_actions.jsx
index e0403529e..6a5cccb81 100644
--- a/webapp/actions/team_actions.jsx
+++ b/webapp/actions/team_actions.jsx
@@ -36,6 +36,24 @@ export function createTeam(team, onSuccess, onError) {
);
}
+export function updateTeam(team, onSuccess, onError) {
+ Client.updateTeam(team,
+ (rteam) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.UPDATE_TEAM,
+ team: rteam
+ });
+
+ browserHistory.push('/' + rteam.name + '/channels/town-square');
+
+ if (onSuccess) {
+ onSuccess(rteam);
+ }
+ },
+ onError
+ );
+}
+
export function removeUserFromTeam(teamId, userId, success, error) {
Client.removeUserFromTeam(
teamId,
diff --git a/webapp/actions/user_actions.jsx b/webapp/actions/user_actions.jsx
index 9b5bc985c..fefca79f7 100644
--- a/webapp/actions/user_actions.jsx
+++ b/webapp/actions/user_actions.jsx
@@ -318,6 +318,24 @@ export function autocompleteUsersInTeam(username, success, error) {
);
}
+export function updateUser(username, success, error) {
+ Client.updateUser(
+ username,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'updateUser');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
export function generateMfaSecret(success, error) {
Client.generateMfaSecret(
(data) => {
diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx
index 60ed44fc9..bf56a148a 100644
--- a/webapp/actions/websocket_actions.jsx
+++ b/webapp/actions/websocket_actions.jsx
@@ -184,7 +184,7 @@ function handleNewPostEvent(msg) {
function handlePostEditEvent(msg) {
// Store post
const post = JSON.parse(msg.data.post);
- PostStore.storePost(post);
+ PostStore.storePost(post, false);
PostStore.emitChange();
// Update channel state
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx
index 3ce6977f6..a7d7a5c8a 100644
--- a/webapp/client/client.jsx
+++ b/webapp/client/client.jsx
@@ -201,12 +201,12 @@ export default class Client {
if (errorCallback) {
errorCallback(e, err, res);
- return;
}
+ return;
}
if (successCallback) {
- if (res.body) {
+ if (res && res.body) {
successCallback(res.body, res);
} else {
console.error('Missing response body for ' + methodName); // eslint-disable-line no-console
@@ -921,6 +921,15 @@ export default class Client {
end(this.handleResponse.bind(this, 'getUser', success, error));
}
+ getByUsername(userName, success, error) {
+ request.
+ get(`${this.getUsersRoute()}/name/${userName}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getByUsername', success, error));
+ }
+
login(loginId, password, mfaToken, success, error) {
this.doLogin({login_id: loginId, password, token: mfaToken}, success, error);
@@ -1457,6 +1466,18 @@ export default class Client {
this.track('api', 'api_integrations_created');
}
+ editCommand(command, success, error) {
+ request.
+ post(`${this.getCommandsRoute()}/update`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(command).
+ end(this.handleResponse.bind(this, 'editCommand', success, error));
+
+ this.track('api', 'api_integrations_created');
+ }
+
deleteCommand(commandId, success, error) {
request.
post(`${this.getCommandsRoute()}/delete`).
@@ -1496,7 +1517,7 @@ export default class Client {
set(this.defaultHeaders).
type('application/json').
accept('application/json').
- send(post).
+ send({...post, create_at: 0}).
end(this.handleResponse.bind(this, 'createPost', success, error));
this.track('api', 'api_posts_create', post.channel_id, 'length', post.message.length);
diff --git a/webapp/client/web_client.jsx b/webapp/client/web_client.jsx
index 5d2696c37..85ced325e 100644
--- a/webapp/client/web_client.jsx
+++ b/webapp/client/web_client.jsx
@@ -14,26 +14,26 @@ class WebClientClass extends Client {
constructor() {
super();
this.enableLogErrorsToConsole(true);
- TeamStore.addChangeListener(this.onTeamStoreChanged);
+ TeamStore.addChangeListener(this.onTeamStoreChanged.bind(this));
}
- onTeamStoreChanged = () => {
+ onTeamStoreChanged() {
this.setTeamId(TeamStore.getCurrentId());
}
- track = (category, action, label, property, value) => {
+ track(category, action, label, property, value) {
if (global.window && global.window.analytics) {
global.window.analytics.track(action, {category, label, property, value});
}
}
- trackPage = () => {
+ trackPage() {
if (global.window && global.window.analytics) {
global.window.analytics.page();
}
}
- handleError = (err, res) => { // eslint-disable-line no-unused-vars
+ handleError(err, res) {
if (err.status === HTTP_UNAUTHORIZED && res.req.url !== '/api/v3/users/login') {
GlobalActions.emitUserLoggedOutEvent('/login');
}
diff --git a/webapp/client/webrtc_session.jsx b/webapp/client/webrtc_session.jsx
index 9ee7fcd5a..df60a1053 100644
--- a/webapp/client/webrtc_session.jsx
+++ b/webapp/client/webrtc_session.jsx
@@ -1951,7 +1951,7 @@ export default class WebrtcSession {
return (trickle === true);
}
- unbindWebSocket = (onUnbindMessage, onUnbindError) => {
+ unbindWebSocket(onUnbindMessage, onUnbindError) {
for (var eventName in this.wsHandlers) {
if (this.wsHandlers.hasOwnProperty(eventName)) {
this.ws.removeEventListener(eventName, this.wsHandlers[eventName]);
@@ -1962,5 +1962,5 @@ export default class WebrtcSession {
if (this.wsKeepaliveTimeoutId) {
clearTimeout(this.wsKeepaliveTimeoutId);
}
- };
-} \ No newline at end of file
+ }
+}
diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx
index f39bb8b6b..25a06cecf 100644
--- a/webapp/components/admin_console/admin_sidebar.jsx
+++ b/webapp/components/admin_console/admin_sidebar.jsx
@@ -192,6 +192,7 @@ export default class AdminSidebar extends React.Component {
let ldapSettings = null;
let samlSettings = null;
let clusterSettings = null;
+ let metricsSettings = null;
let complianceSettings = null;
let license = null;
@@ -241,6 +242,20 @@ export default class AdminSidebar extends React.Component {
);
}
+ if (global.window.mm_license.Metrics === 'true') {
+ metricsSettings = (
+ <AdminSidebarSection
+ name='metrics'
+ title={
+ <FormattedMessage
+ id='admin.sidebar.metrics'
+ defaultMessage='Performance Monitoring (Beta)'
+ />
+ }
+ />
+ );
+ }
+
if (global.window.mm_license.SAML === 'true') {
samlSettings = (
<AdminSidebarSection
@@ -716,6 +731,7 @@ export default class AdminSidebar extends React.Component {
}
/>
{clusterSettings}
+ {metricsSettings}
</AdminSidebarSection>
</AdminSidebarCategory>
{this.renderTeams()}
diff --git a/webapp/components/admin_console/cluster_settings.jsx b/webapp/components/admin_console/cluster_settings.jsx
index 8aab905e4..bbd135e50 100644
--- a/webapp/components/admin_console/cluster_settings.jsx
+++ b/webapp/components/admin_console/cluster_settings.jsx
@@ -60,7 +60,7 @@ export default class ClusterSettings extends AdminSettings {
);
}
- overrideHandleChange = (id, value) => {
+ overrideHandleChange(id, value) {
this.setState({
showWarning: true
});
@@ -185,4 +185,4 @@ export default class ClusterSettings extends AdminSettings {
</SettingsGroup>
);
}
-} \ No newline at end of file
+}
diff --git a/webapp/components/admin_console/cluster_table.jsx b/webapp/components/admin_console/cluster_table.jsx
index 4aca796a0..0a2755c4a 100644
--- a/webapp/components/admin_console/cluster_table.jsx
+++ b/webapp/components/admin_console/cluster_table.jsx
@@ -176,4 +176,4 @@ export default class ClusterTable extends React.Component {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/webapp/components/admin_console/cluster_table_container.jsx b/webapp/components/admin_console/cluster_table_container.jsx
index 5dad56469..aad5753b7 100644
--- a/webapp/components/admin_console/cluster_table_container.jsx
+++ b/webapp/components/admin_console/cluster_table_container.jsx
@@ -18,7 +18,7 @@ export default class ClusterTableContainer extends React.Component {
};
}
- load = () => {
+ load() {
Client.getClusterStatus(
(data) => {
this.setState({
@@ -44,7 +44,7 @@ export default class ClusterTableContainer extends React.Component {
}
}
- reload = (e) => {
+ reload(e) {
if (e) {
e.preventDefault();
}
@@ -68,4 +68,4 @@ export default class ClusterTableContainer extends React.Component {
/>
);
}
-} \ No newline at end of file
+}
diff --git a/webapp/components/admin_console/file_upload_setting.jsx b/webapp/components/admin_console/file_upload_setting.jsx
index a7df16c0a..0c1efc168 100644
--- a/webapp/components/admin_console/file_upload_setting.jsx
+++ b/webapp/components/admin_console/file_upload_setting.jsx
@@ -108,7 +108,7 @@ export default class FileUploadSetting extends Setting {
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
ref='upload_button'
- data-loading-text={`<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ${this.props.uploadingText}`}
+ data-loading-text={`<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> ${this.props.uploadingText}`}
>
<FormattedMessage
id='admin.file_upload.uploadFile'
diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx
index ad0277b7f..8dc0c1e2e 100644
--- a/webapp/components/admin_console/logs.jsx
+++ b/webapp/components/admin_console/logs.jsx
@@ -26,6 +26,12 @@ export default class Logs extends React.Component {
AsyncClient.getLogs();
}
+ componentDidUpdate() {
+ // Scroll Down to get the latest logs
+ var node = this.refs.logPanel;
+ node.scrollTop = node.scrollHeight;
+ }
+
componentWillUnmount() {
AdminStore.removeLogChangeListener(this.onLogListenerChange);
}
@@ -93,7 +99,10 @@ export default class Logs extends React.Component {
defaultMessage='Reload'
/>
</button>
- <div className='log__panel'>
+ <div
+ ref='logPanel'
+ className='log__panel'
+ >
{content}
</div>
</div>
diff --git a/webapp/components/admin_console/metrics_settings.jsx b/webapp/components/admin_console/metrics_settings.jsx
new file mode 100644
index 000000000..dd031047e
--- /dev/null
+++ b/webapp/components/admin_console/metrics_settings.jsx
@@ -0,0 +1,96 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import AdminSettings from './admin_settings.jsx';
+import BooleanSetting from './boolean_setting.jsx';
+import TextSetting from './text_setting.jsx';
+
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import SettingsGroup from './settings_group.jsx';
+
+import * as Utils from 'utils/utils.jsx';
+
+export default class MetricsSettings extends AdminSettings {
+ constructor(props) {
+ super(props);
+
+ this.getConfigFromState = this.getConfigFromState.bind(this);
+ this.renderSettings = this.renderSettings.bind(this);
+ }
+
+ getConfigFromState(config) {
+ config.MetricsSettings.Enable = this.state.enable;
+ config.MetricsSettings.ListenAddress = this.state.listenAddress;
+
+ return config;
+ }
+
+ getStateFromConfig(config) {
+ const settings = config.MetricsSettings;
+
+ return {
+ enable: settings.Enable,
+ listenAddress: settings.ListenAddress
+ };
+ }
+
+ renderTitle() {
+ return (
+ <h3>
+ <FormattedMessage
+ id='admin.advance.metrics'
+ defaultMessage='Performance Monitoring (Beta)'
+ />
+ </h3>
+ );
+ }
+
+ renderSettings() {
+ const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Metrics === 'true';
+ if (!licenseEnabled) {
+ return null;
+ }
+
+ return (
+ <SettingsGroup>
+ <BooleanSetting
+ id='enable'
+ label={
+ <FormattedMessage
+ id='admin.metrics.enableTitle'
+ defaultMessage='Enable Performance Monitoring:'
+ />
+ }
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.metrics.enableDescription'
+ defaultMessage='When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href="http://docs.mattermost.com/deployment/metrics.html" target="_blank">documentation</a> to learn more about configuring performance monitoring for Mattermost.'
+ />
+ }
+ value={this.state.enable}
+ onChange={this.handleChange}
+ />
+ <TextSetting
+ id='listenAddress'
+ label={
+ <FormattedMessage
+ id='admin.metrics.listenAddressTitle'
+ defaultMessage='Listen Address:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.metrics.listenAddressEx', 'Ex ":8067"')}
+ helpText={
+ <FormattedMessage
+ id='admin.metrics.listenAddressDesc'
+ defaultMessage='The address the server will listen on to expose performance metrics.'
+ />
+ }
+ value={this.state.listenAddress}
+ onChange={this.handleChange}
+ />
+ </SettingsGroup>
+ );
+ }
+}
diff --git a/webapp/components/admin_console/users_and_teams_settings.jsx b/webapp/components/admin_console/users_and_teams_settings.jsx
index dd19005c8..2cb5b4e51 100644
--- a/webapp/components/admin_console/users_and_teams_settings.jsx
+++ b/webapp/components/admin_console/users_and_teams_settings.jsx
@@ -32,6 +32,7 @@ export default class UsersAndTeamsSettings extends AdminSettings {
config.TeamSettings.RestrictCreationToDomains = this.state.restrictCreationToDomains;
config.TeamSettings.RestrictDirectMessage = this.state.restrictDirectMessage;
config.TeamSettings.MaxChannelsPerTeam = this.parseIntNonZero(this.state.maxChannelsPerTeam, Constants.DEFAULT_MAX_CHANNELS_PER_TEAM);
+ config.TeamSettings.MaxNotificationsPerChannel = this.parseIntNonZero(this.state.maxNotificationsPerChannel, Constants.DEFAULT_MAX_NOTIFICATIONS_PER_CHANNEL);
return config;
}
@@ -43,7 +44,8 @@ export default class UsersAndTeamsSettings extends AdminSettings {
maxUsersPerTeam: config.TeamSettings.MaxUsersPerTeam,
restrictCreationToDomains: config.TeamSettings.RestrictCreationToDomains,
restrictDirectMessage: config.TeamSettings.RestrictDirectMessage,
- maxChannelsPerTeam: config.TeamSettings.MaxChannelsPerTeam
+ maxChannelsPerTeam: config.TeamSettings.MaxChannelsPerTeam,
+ maxNotificationsPerChannel: config.TeamSettings.MaxNotificationsPerChannel
};
}
@@ -132,6 +134,24 @@ export default class UsersAndTeamsSettings extends AdminSettings {
onChange={this.handleChange}
/>
<TextSetting
+ id='maxNotificationsPerChannel'
+ label={
+ <FormattedMessage
+ id='admin.team.maxNotificationsPerChannelTitle'
+ defaultMessage='Max Notifications Per Channel:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.team.maxNotificationsPerChannelExample', 'Ex "1000"')}
+ helpText={
+ <FormattedMessage
+ id='admin.team.maxNotificationsPerChannelDescription'
+ defaultMessage='Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.'
+ />
+ }
+ value={this.state.maxNotificationsPerChannel}
+ onChange={this.handleChange}
+ />
+ <TextSetting
id='restrictCreationToDomains'
label={
<FormattedMessage
diff --git a/webapp/components/admin_console/webrtc_settings.jsx b/webapp/components/admin_console/webrtc_settings.jsx
index cea8e2226..995a02a0c 100644
--- a/webapp/components/admin_console/webrtc_settings.jsx
+++ b/webapp/components/admin_console/webrtc_settings.jsx
@@ -15,23 +15,10 @@ export default class WebrtcSettings extends AdminSettings {
constructor(props) {
super(props);
- this.canSave = this.canSave.bind(this);
- this.handleAgreeChange = this.handleAgreeChange.bind(this);
-
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
- canSave() {
- return !this.state.enableWebrtc || this.state.agree;
- }
-
- handleAgreeChange(e) {
- this.setState({
- agree: e.target.checked
- });
- }
-
getConfigFromState(config) {
config.WebrtcSettings.Enable = this.state.enableWebrtc;
config.WebrtcSettings.GatewayWebsocketUrl = this.state.gatewayWebsocketUrl;
@@ -57,8 +44,7 @@ export default class WebrtcSettings extends AdminSettings {
stunURI: settings.StunURI,
turnURI: settings.TurnURI,
turnUsername: settings.TurnUsername,
- turnSharedKey: settings.TurnSharedKey,
- agree: settings.Enable
+ turnSharedKey: settings.TurnSharedKey
};
}
@@ -74,25 +60,6 @@ export default class WebrtcSettings extends AdminSettings {
}
renderSettings() {
- const tosCheckbox = (
- <div className='form-group'>
- <div className='col-sm-4'/>
- <div className='col-sm-8'>
- <input
- type='checkbox'
- ref='agree'
- checked={this.state.agree}
- onChange={this.handleAgreeChange}
- disabled={!this.state.enableWebrtc}
- />
- <FormattedHTMLMessage
- id='admin.webrtc.agree'
- defaultMessage=' I understand and accept the Mattermost Hosted WebRTC Service <a href="https://about.mattermost.com/webrtc-terms/" target="_blank">Terms of Service</a> and <a href="https://about.mattermost.com/webrtc-privacy/" target="_blank">Privacy Policy</a>.'
- />
- </div>
- </div>
- );
-
return (
<SettingsGroup>
<BooleanSetting
@@ -112,7 +79,6 @@ export default class WebrtcSettings extends AdminSettings {
value={this.state.enableWebrtc}
onChange={this.handleChange}
/>
- {tosCheckbox}
<TextSetting
id='gatewayWebsocketUrl'
label={
diff --git a/webapp/components/change_url_modal.jsx b/webapp/components/change_url_modal.jsx
index fa115cf36..c9d2f3245 100644
--- a/webapp/components/change_url_modal.jsx
+++ b/webapp/components/change_url_modal.jsx
@@ -145,6 +145,7 @@ export default class ChangeUrlModal extends React.Component {
<Modal
show={this.props.show}
onHide={this.doCancel}
+ onExited={this.props.onModalExited}
>
<Modal.Header closeButton={true}>
<Modal.Title>{this.props.title}</Modal.Title>
@@ -226,5 +227,6 @@ ChangeUrlModal.propTypes = {
currentURL: React.PropTypes.string,
serverError: React.PropTypes.node,
onModalSubmit: React.PropTypes.func.isRequired,
+ onModalExited: React.PropTypes.func.optional,
onModalDismissed: React.PropTypes.func.isRequired
};
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index 50b860287..d8110aa5a 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -308,6 +308,13 @@ export default class ChannelHeader extends React.Component {
if (isOffline || busy) {
circleClass = 'offline';
+ webrtcMessage = (
+ <FormattedMessage
+ id='channel_header.webrtc.offline'
+ defaultMessage='The user is offline'
+ />
+ );
+
if (busy) {
webrtcMessage = (
<FormattedMessage
@@ -325,6 +332,10 @@ export default class ChannelHeader extends React.Component {
);
}
+ const webrtcTooltip = (
+ <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
+ );
+
webrtc = (
<div className='webrtc__header'>
<a
@@ -332,28 +343,18 @@ export default class ChannelHeader extends React.Component {
onClick={() => this.initWebrtc(otherUserId, !isOffline)}
disabled={isOffline}
>
- <svg
- id='webrtc-btn'
- className='webrtc__button'
- xmlns='http://www.w3.org/2000/svg'
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='bottom'
+ overlay={webrtcTooltip}
>
- <circle
- className={circleClass}
- cx='16'
- cy='16'
- r='18'
+ <div
+ id='webrtc-btn'
+ className={'webrtc__button ' + circleClass}
>
- <title>
- {webrtcMessage}
- </title>
- </circle>
- <path
- className='off'
- transform='scale(0.4), translate(17,16)'
- d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
- fill='white'
- />
- </svg>
+ <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
+ </div>
+ </OverlayTrigger>
</a>
</div>
);
@@ -648,10 +649,10 @@ export default class ChannelHeader extends React.Component {
id='channelHeader.removeFromFavorites'
defaultMessage='Remove from Favorites'
/> :
- <FormattedMessage
- id='channelHeader.addToFavorites'
- defaultMessage='Add to Favorites'
- />}
+ <FormattedMessage
+ id='channelHeader.addToFavorites'
+ defaultMessage='Add to Favorites'
+ />}
</Tooltip>
);
const toggleFavorite = (
diff --git a/webapp/components/code_preview.jsx b/webapp/components/code_preview.jsx
index b06d9855a..6afe45c2e 100644
--- a/webapp/components/code_preview.jsx
+++ b/webapp/components/code_preview.jsx
@@ -58,8 +58,12 @@ export default class CodePreview extends React.Component {
}
handleReceivedCode(data) {
+ let code = data;
+ if (data.nodeName === '#document') {
+ code = new XMLSerializer().serializeToString(data);
+ }
this.setState({
- code: data,
+ code,
loading: false,
success: true
});
diff --git a/webapp/components/create_team/components/display_name.jsx b/webapp/components/create_team/components/display_name.jsx
index 50e7b340b..a557a48c5 100644
--- a/webapp/components/create_team/components/display_name.jsx
+++ b/webapp/components/create_team/components/display_name.jsx
@@ -27,10 +27,24 @@ export default class TeamSignupDisplayNamePage extends React.Component {
var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim();
if (!displayName) {
- this.setState({nameError: Utils.localizeMessage('create_team.display_name.required', 'This field is required')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.display_name.required'
+ defaultMessage='This field is required'
+ />)
+ });
return;
} else if (displayName.length < Constants.MIN_TEAMNAME_LENGTH || displayName.length > Constants.MAX_TEAMNAME_LENGTH) {
- this.setState({nameError: Utils.localizeMessage('create_team.display_name.charLength', 'Name must be 2 or more characters up to a maximum of 15')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.display_name.charLength'
+ defaultMessage='Name must be {min} or more characters up to a maximum of {max}'
+ values={{
+ min: Constants.MIN_TEAMNAME_LENGTH,
+ max: Constants.MAX_TEAMNAME_LENGTH
+ }}
+ />)
+ });
return;
}
diff --git a/webapp/components/create_team/components/team_url.jsx b/webapp/components/create_team/components/team_url.jsx
index 4bea240da..cff0002e0 100644
--- a/webapp/components/create_team/components/team_url.jsx
+++ b/webapp/components/create_team/components/team_url.jsx
@@ -42,26 +42,47 @@ export default class TeamUrl extends React.Component {
const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
if (!name) {
- this.setState({nameError: Utils.localizeMessage('create_team.team_url.required', 'This field is required')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.team_url.required'
+ defaultMessage='This field is required'
+ />)
+ });
return;
}
if (cleanedName.length < Constants.MIN_TEAMNAME_LENGTH || cleanedName.length > Constants.MAX_TEAMNAME_LENGTH) {
- this.setState({nameError: Utils.localizeMessage('create_team.team_url.charLength', 'Name must be 4 or more characters up to a maximum of 15')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.team_url.charLength'
+ defaultMessage='Name must be {min} or more characters up to a maximum of {max}'
+ values={{
+ min: Constants.MIN_TEAMNAME_LENGTH,
+ max: Constants.MAX_TEAMNAME_LENGTH
+ }}
+ />)
+ });
return;
}
if (cleanedName !== name || !urlRegex.test(name)) {
- this.setState({nameError: Utils.localizeMessage('create_team.team_url.regex', "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.")});
- return;
- } else if (cleanedName.length < Constants.MIN_TEAMNAME_LENGTH || cleanedName.length > Constants.MAX_TEAMNAME_LENGTH) {
- this.setState({nameError: Utils.localizeMessage('create_team.team_url.charLength', 'Name must be 2 or more characters up to a maximum of 15')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.team_url.regex'
+ defaultMessage="Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."
+ />)
+ });
return;
}
for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
- this.setState({nameError: Utils.localizeMessage('create_team.team_url.taken', 'URL is taken or contains a reserved word')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.team_url.taken'
+ defaultMessage='URL is taken or contains a reserved word'
+ />)
+ });
return;
}
}
@@ -74,7 +95,12 @@ export default class TeamUrl extends React.Component {
checkIfTeamExists(name,
(foundTeam) => {
if (foundTeam) {
- this.setState({nameError: Utils.localizeMessage('create_team.team_url.unavailable', 'This URL is unavailable. Please try another.')});
+ this.setState({nameError: (
+ <FormattedMessage
+ id='create_team.team_url.unavailable'
+ defaultMessage='This URL is unavailable. Please try another.'
+ />)
+ });
this.setState({isLoading: false});
return;
}
diff --git a/webapp/components/integrations/components/edit_command.jsx b/webapp/components/integrations/components/edit_command.jsx
new file mode 100644
index 000000000..395c977ca
--- /dev/null
+++ b/webapp/components/integrations/components/edit_command.jsx
@@ -0,0 +1,731 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import IntegrationStore from 'stores/integration_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+import {loadTeamCommands} from 'actions/integration_actions.jsx';
+import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
+import {FormattedMessage} from 'react-intl';
+import FormError from 'components/form_error.jsx';
+import {browserHistory, Link} from 'react-router/es6';
+import SpinnerButton from 'components/spinner_button.jsx';
+import Constants from 'utils/constants.jsx';
+import ConfirmModal from 'components/confirm_modal.jsx';
+
+const REQUEST_POST = 'P';
+const REQUEST_GET = 'G';
+
+export default class EditCommand extends React.Component {
+ static get propTypes() {
+ return {
+ team: React.propTypes.object.isRequired,
+ location: React.PropTypes.object
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
+
+ this.submitCommand = this.submitCommand.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleUpdate = this.handleUpdate.bind(this);
+ this.handleConfirmModal = this.handleConfirmModal.bind(this);
+ this.confirmModalDismissed = this.confirmModalDismissed.bind(this);
+
+ this.updateDisplayName = this.updateDisplayName.bind(this);
+ this.updateDescription = this.updateDescription.bind(this);
+ this.updateTrigger = this.updateTrigger.bind(this);
+ this.updateUrl = this.updateUrl.bind(this);
+ this.updateMethod = this.updateMethod.bind(this);
+ this.updateUsername = this.updateUsername.bind(this);
+ this.updateIconUrl = this.updateIconUrl.bind(this);
+ this.updateAutocomplete = this.updateAutocomplete.bind(this);
+ this.updateAutocompleteHint = this.updateAutocompleteHint.bind(this);
+ this.updateAutocompleteDescription = this.updateAutocompleteDescription.bind(this);
+
+ this.originalCommand = null;
+ this.newCommand = null;
+
+ const teamId = TeamStore.getCurrentId();
+
+ this.state = {
+ displayName: '',
+ description: '',
+ trigger: '',
+ url: '',
+ method: REQUEST_POST,
+ username: '',
+ iconUrl: '',
+ autocomplete: false,
+ autocompleteHint: '',
+ autocompleteDescription: '',
+ saving: false,
+ serverError: '',
+ clientError: null,
+ showConfirmModal: false,
+ commands: IntegrationStore.getCommands(teamId),
+ loading: !IntegrationStore.hasReceivedCommands(teamId)
+ };
+ }
+
+ componentDidMount() {
+ IntegrationStore.addChangeListener(this.handleIntegrationChange);
+
+ if (window.mm_config.EnableCommands === 'true') {
+ loadTeamCommands();
+ }
+ }
+
+ componentWillUnmount() {
+ IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ }
+
+ handleConfirmModal() {
+ this.setState({showConfirmModal: true});
+ }
+
+ confirmModalDismissed() {
+ this.setState({showConfirmModal: false});
+ }
+
+ submitCommand() {
+ AsyncClient.editCommand(
+ this.newCmd,
+ browserHistory.push('/' + this.props.team.name + '/integrations/commands'),
+ (err) => {
+ this.setState({
+ saving: false,
+ serverError: err.message
+ });
+ }
+ );
+ }
+
+ handleUpdate() {
+ this.setState({
+ saving: true,
+ serverError: '',
+ clientError: ''
+ });
+
+ this.submitCommand();
+ }
+
+ handleIntegrationChange() {
+ const teamId = TeamStore.getCurrentId();
+
+ this.setState({
+ commands: IntegrationStore.getCommands(teamId),
+ loading: !IntegrationStore.hasReceivedCommands(teamId)
+ });
+
+ if (!this.state.loading) {
+ this.originalCommand = this.state.commands.filter((command) => command.id === this.props.location.query.id)[0];
+
+ this.setState({
+ displayName: this.originalCommand.display_name,
+ description: this.originalCommand.description,
+ trigger: this.originalCommand.trigger,
+ url: this.originalCommand.url,
+ method: this.originalCommand.method,
+ username: this.originalCommand.username,
+ iconUrl: this.originalCommand.icon_url,
+ autocomplete: this.originalCommand.auto_complete,
+ autocompleteHint: this.originalCommand.auto_complete_hint,
+ autocompleteDescription: this.originalCommand.auto_complete_desc
+ });
+ }
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+
+ if (this.state.saving) {
+ return;
+ }
+
+ this.setState({
+ saving: true,
+ serverError: '',
+ clientError: ''
+ });
+
+ let triggerWord = this.state.trigger.trim().toLowerCase();
+ if (triggerWord.indexOf('/') === 0) {
+ triggerWord = triggerWord.substr(1);
+ }
+
+ const command = {
+ display_name: this.state.displayName,
+ description: this.state.description,
+ trigger: triggerWord,
+ url: this.state.url.trim(),
+ method: this.state.method,
+ username: this.state.username,
+ icon_url: this.state.iconUrl,
+ auto_complete: this.state.autocomplete
+ };
+
+ if (this.originalCommand.id) {
+ command.id = this.originalCommand.id;
+ }
+
+ if (command.auto_complete) {
+ command.auto_complete_desc = this.state.autocompleteDescription;
+ command.auto_complete_hint = this.state.autocompleteHint;
+ }
+
+ if (!command.trigger) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.triggerRequired'
+ defaultMessage='A trigger word is required'
+ />
+ )
+ });
+
+ return;
+ }
+
+ if (command.trigger.indexOf('/') === 0) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.triggerInvalidSlash'
+ defaultMessage='A trigger word cannot begin with a /'
+ />
+ )
+ });
+
+ return;
+ }
+
+ if (command.trigger.indexOf(' ') !== -1) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.triggerInvalidSpace'
+ defaultMessage='A trigger word must not contain spaces'
+ />
+ )
+ });
+ return;
+ }
+
+ if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH || command.trigger.length > Constants.MAX_TRIGGER_LENGTH) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.triggerInvalidLength'
+ defaultMessage='A trigger word must contain between {min} and {max} characters'
+ values={{
+ min: Constants.MIN_TRIGGER_LENGTH,
+ max: Constants.MAX_TRIGGER_LENGTH
+ }}
+ />
+ )
+ });
+
+ return;
+ }
+
+ if (!command.url) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.urlRequired'
+ defaultMessage='A request URL is required'
+ />
+ )
+ });
+
+ return;
+ }
+
+ this.newCmd = command;
+
+ if (this.originalCommand.url !== this.newCmd.url || this.originalCommand.trigger !== this.newCmd.trigger || this.originalCommand.method !== this.newCmd.method) {
+ this.handleConfirmModal();
+ this.setState({
+ saving: false
+ });
+ } else {
+ this.submitCommand();
+ }
+ }
+
+ updateDisplayName(e) {
+ this.setState({
+ displayName: e.target.value
+ });
+ }
+
+ updateDescription(e) {
+ this.setState({
+ description: e.target.value
+ });
+ }
+
+ updateTrigger(e) {
+ this.setState({
+ trigger: e.target.value
+ });
+ }
+
+ updateUrl(e) {
+ this.setState({
+ url: e.target.value
+ });
+ }
+
+ updateMethod(e) {
+ this.setState({
+ method: e.target.value
+ });
+ }
+
+ updateUsername(e) {
+ this.setState({
+ username: e.target.value
+ });
+ }
+
+ updateIconUrl(e) {
+ this.setState({
+ iconUrl: e.target.value
+ });
+ }
+
+ updateAutocomplete(e) {
+ this.setState({
+ autocomplete: e.target.checked
+ });
+ }
+
+ updateAutocompleteHint(e) {
+ this.setState({
+ autocompleteHint: e.target.value
+ });
+ }
+
+ updateAutocompleteDescription(e) {
+ this.setState({
+ autocompleteDescription: e.target.value
+ });
+ }
+
+ render() {
+ const confirmButton = (
+ <FormattedMessage
+ id='update_command.update'
+ defaultMessage='Update'
+ />
+ );
+
+ const confirmTitle = (
+ <FormattedMessage
+ id='update_command.confirm'
+ defaultMessage='Edit Slash Command'
+ />
+ );
+
+ const confirmMessage = (
+ <FormattedMessage
+ id='update_command.question'
+ defaultMessage='Your changes may break the existing slash command. Are you sure you would like to update it?'
+ />
+ );
+
+ let autocompleteFields = null;
+ if (this.state.autocomplete) {
+ autocompleteFields = [(
+ <div
+ key='autocompleteHint'
+ className='form-group'
+ >
+ <label
+ className='control-label col-sm-4'
+ htmlFor='autocompleteHint'
+ >
+ <FormattedMessage
+ id='add_command.autocompleteHint'
+ defaultMessage='Autocomplete Hint'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='autocompleteHint'
+ type='text'
+ maxLength='1024'
+ className='form-control'
+ value={this.state.autocompleteHint}
+ onChange={this.updateAutocompleteHint}
+ placeholder={Utils.localizeMessage('add_command.autocompleteHint.placeholder', 'Example: [Patient Name]')}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.autocompleteHint.help'
+ defaultMessage='(Optional) Arguments associated with your slash command, displayed as help in the autocomplete list.'
+ />
+ </div>
+ </div>
+ </div>
+ ),
+ (
+ <div
+ key='autocompleteDescription'
+ className='form-group'
+ >
+ <label
+ className='control-label col-sm-4'
+ htmlFor='autocompleteDescription'
+ >
+ <FormattedMessage
+ id='add_command.autocompleteDescription'
+ defaultMessage='Autocomplete Description'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='description'
+ type='text'
+ maxLength='128'
+ className='form-control'
+ value={this.state.autocompleteDescription}
+ onChange={this.updateAutocompleteDescription}
+ placeholder={Utils.localizeMessage('add_command.autocompleteDescription.placeholder', 'Example: "Returns search results for patient records"')}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.autocompleteDescription.help'
+ defaultMessage='(Optional) Short description of slash command for the autocomplete list.'
+ />
+ </div>
+ </div>
+ </div>
+ )];
+ }
+
+ return (
+ <div className='backstage-content row'>
+ <BackstageHeader>
+ <Link to={'/' + this.props.team.name + '/integrations/commands'}>
+ <FormattedMessage
+ id='installed_command.header'
+ defaultMessage='Slash Commands'
+ />
+ </Link>
+ <FormattedMessage
+ id='integrations.edit'
+ defaultMessage='Edit'
+ />
+ </BackstageHeader>
+ <div className='backstage-form'>
+ <form
+ className='form-horizontal'
+ onSubmit={this.handleSubmit}
+ >
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='displayName'
+ >
+ <FormattedMessage
+ id='add_command.displayName'
+ defaultMessage='Display Name'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='displayName'
+ type='text'
+ maxLength='64'
+ className='form-control'
+ value={this.state.displayName}
+ onChange={this.updateDisplayName}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.displayName.help'
+ defaultMessage='Display name for your slash command made of up to 64 characters.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='description'
+ >
+ <FormattedMessage
+ id='add_command.description'
+ defaultMessage='Description'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='description'
+ type='text'
+ maxLength='128'
+ className='form-control'
+ value={this.state.description}
+ onChange={this.updateDescription}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.description.help'
+ defaultMessage='Description for your incoming webhook.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='trigger'
+ >
+ <FormattedMessage
+ id='add_command.trigger'
+ defaultMessage='Command Trigger Word'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='trigger'
+ type='text'
+ maxLength={Constants.MAX_TRIGGER_LENGTH}
+ className='form-control'
+ value={this.state.trigger}
+ onChange={this.updateTrigger}
+ placeholder={Utils.localizeMessage('add_command.trigger.placeholder', 'Command trigger e.g. "hello" not including the slash')}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.trigger.help'
+ defaultMessage='Trigger word must be unique, and cannot begin with a slash or contain any spaces.'
+ />
+ </div>
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.trigger.helpExamples'
+ defaultMessage='Examples: client, employee, patient, weather'
+ />
+ </div>
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.trigger.helpReserved'
+ defaultMessage='Reserved: {link}'
+ values={{
+ link: (
+ <a
+ href='https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands'
+ target='_blank'
+ rel='noopener noreferrer'
+ >
+ <FormattedMessage
+ id='add_command.trigger.helpReservedLinkText'
+ defaultMessage='see list of built-in slash commands'
+ />
+ </a>
+ )
+ }}
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='url'
+ >
+ <FormattedMessage
+ id='add_command.url'
+ defaultMessage='Request URL'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='url'
+ type='text'
+ maxLength='1024'
+ className='form-control'
+ value={this.state.url}
+ onChange={this.updateUrl}
+ placeholder={Utils.localizeMessage('add_command.url.placeholder', 'Must start with http:// or https://')}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.url.help'
+ defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='method'
+ >
+ <FormattedMessage
+ id='add_command.method'
+ defaultMessage='Request Method'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <select
+ id='method'
+ className='form-control'
+ value={this.state.method}
+ onChange={this.updateMethod}
+ >
+ <option value={REQUEST_POST}>
+ {Utils.localizeMessage('add_command.method.post', 'POST')}
+ </option>
+ <option value={REQUEST_GET}>
+ {Utils.localizeMessage('add_command.method.get', 'GET')}
+ </option>
+ </select>
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.method.help'
+ defaultMessage='The type of command request issued to the Request URL.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='username'
+ >
+ <FormattedMessage
+ id='add_command.username'
+ defaultMessage='Response Username'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='username'
+ type='text'
+ maxLength='64'
+ className='form-control'
+ value={this.state.username}
+ onChange={this.updateUsername}
+ placholder={Utils.localizeMessage('add_command.username.placeholder', 'Username')}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.username.help'
+ defaultMessage='(Optional) Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols "-", "_", and "." .'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='iconUrl'
+ >
+ <FormattedMessage
+ id='add_command.iconUrl'
+ defaultMessage='Response Icon'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8'>
+ <input
+ id='iconUrl'
+ type='text'
+ maxLength='1024'
+ className='form-control'
+ value={this.state.iconUrl}
+ onChange={this.updateIconUrl}
+ placeholder={Utils.localizeMessage('add_command.iconUrl.placeholder', 'https://www.example.com/myicon.png')}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.iconUrl.help'
+ defaultMessage='(Optional) Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='autocomplete'
+ >
+ <FormattedMessage
+ id='add_command.autocomplete'
+ defaultMessage='Autocomplete'
+ />
+ </label>
+ <div className='col-md-5 col-sm-8 checkbox'>
+ <input
+ id='autocomplete'
+ type='checkbox'
+ checked={this.state.autocomplete}
+ onChange={this.updateAutocomplete}
+ />
+ <div className='form__help'>
+ <FormattedMessage
+ id='add_command.autocomplete.help'
+ defaultMessage='(Optional) Show slash command in autocomplete list.'
+ />
+ </div>
+ </div>
+ </div>
+ {autocompleteFields}
+ <div className='backstage-form__footer'>
+ <FormError
+ type='backstage'
+ errors={[this.state.serverError, this.state.clientError]}
+ />
+ <Link
+ className='btn btn-sm'
+ to={'/' + this.props.team.name + '/integrations/commands'}
+ >
+ <FormattedMessage
+ id='add_command.cancel'
+ defaultMessage='Cancel'
+ />
+ </Link>
+ <SpinnerButton
+ className='btn btn-primary'
+ type='submit'
+ spinning={this.state.saving}
+ onClick={this.handleSubmit}
+ disabled={this.state.loading}
+ >
+ <FormattedMessage
+ id='edit_command.save'
+ defaultMessage='Update'
+ />
+ </SpinnerButton>
+ <ConfirmModal
+ title={confirmTitle}
+ message={confirmMessage}
+ confirmButton={confirmButton}
+ show={this.state.showConfirmModal}
+ onConfirm={this.handleUpdate}
+ onCancel={this.confirmModalDismissed}
+ />
+ </div>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/integrations/components/installed_command.jsx b/webapp/components/integrations/components/installed_command.jsx
index f149a21ac..ecd7d9608 100644
--- a/webapp/components/integrations/components/installed_command.jsx
+++ b/webapp/components/integrations/components/installed_command.jsx
@@ -130,6 +130,15 @@ export default class InstalledCommand extends React.Component {
</a>
{' - '}
<a
+ href={'edit?id=' + command.id}
+ >
+ <FormattedMessage
+ id='installed_integrations.edit'
+ defaultMessage='Edit'
+ />
+ </a>
+ {' - '}
+ <a
href='#'
onClick={this.handleDelete}
>
diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx
index ec4ca2a6a..841061d48 100644
--- a/webapp/components/logged_in.jsx
+++ b/webapp/components/logged_in.jsx
@@ -109,6 +109,10 @@ export default class LoggedIn extends React.Component {
// Listen for user
UserStore.addChangeListener(this.onUserChanged);
+ // Listen for focussed tab/window state
+ window.addEventListener('focus', this.onFocusListener);
+ window.addEventListener('blur', this.onBlurListener);
+
// ???
$('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
@@ -166,6 +170,10 @@ export default class LoggedIn extends React.Component {
$('.modal').off('show.bs.modal');
$(window).off('keydown.preventBackspace');
+
+ // Listen for focussed tab/window state
+ window.removeEventListener('focus', this.onFocusListener);
+ window.removeEventListener('blur', this.onBlurListener);
}
render() {
@@ -177,6 +185,14 @@ export default class LoggedIn extends React.Component {
user: this.state.user
});
}
+
+ onFocusListener() {
+ GlobalActions.emitBrowserFocus(true);
+ }
+
+ onBlurListener() {
+ GlobalActions.emitBrowserFocus(false);
+ }
}
LoggedIn.propTypes = {
diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx
index fd5413c17..ae33e489f 100644
--- a/webapp/components/login/login_controller.jsx
+++ b/webapp/components/login/login_controller.jsx
@@ -150,8 +150,8 @@ export default class LoginController extends React.Component {
query.d,
query.h,
query.id,
- () => {
- this.finishSignin();
+ (team) => {
+ this.finishSignin(team);
},
() => {
// there's not really a good way to deal with this, so just let the user log in like normal
@@ -167,7 +167,6 @@ export default class LoginController extends React.Component {
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
browserHistory.push('/should_verify_email?&email=' + encodeURIComponent(loginId));
- return;
} else if (err.id === 'store.sql_user.get_for_login.app_error' ||
err.id === 'ent.ldap.do_login.user_not_registered.app_error') {
this.setState({
@@ -196,13 +195,15 @@ export default class LoginController extends React.Component {
);
}
- finishSignin() {
+ finishSignin(team) {
GlobalActions.emitInitialLoad(
() => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();
if (query.redirect_to) {
browserHistory.push(query.redirect_to);
+ } else if (team) {
+ browserHistory.push(`/${team.name}`);
} else {
browserHistory.push('/select_team');
}
diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx
index 50ab5224a..f8cf64867 100644
--- a/webapp/components/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels.jsx
@@ -105,7 +105,7 @@ export default class MoreDirectChannels extends React.Component {
let users;
if (this.state.listType === 'any') {
- users = UserStore.getProfileList();
+ users = UserStore.getProfileList(true);
} else {
users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, true);
}
@@ -119,7 +119,7 @@ export default class MoreDirectChannels extends React.Component {
const listType = e.target.value;
let users;
if (listType === 'any') {
- users = UserStore.getProfileList();
+ users = UserStore.getProfileList(true);
} else {
users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true, true);
}
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index d71fec945..0a5f04394 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -501,10 +501,10 @@ export default class Navbar extends React.Component {
id='channelHeader.removeFromFavorites'
defaultMessage='Remove from Favorites'
/> :
- <FormattedMessage
- id='channelHeader.addToFavorites'
- defaultMessage='Add to Favorites'
- />}
+ <FormattedMessage
+ id='channelHeader.addToFavorites'
+ defaultMessage='Add to Favorites'
+ />}
</a>
</li>
);
diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx
index c6c265725..b37e6cf35 100644
--- a/webapp/components/new_channel_flow.jsx
+++ b/webapp/components/new_channel_flow.jsx
@@ -53,6 +53,7 @@ class NewChannelFlow extends React.Component {
super(props);
this.doSubmit = this.doSubmit.bind(this);
+ this.onModalExited = this.onModalExited.bind(this);
this.typeSwitched = this.typeSwitched.bind(this);
this.urlChangeRequested = this.urlChangeRequested.bind(this);
this.urlChangeSubmitted = this.urlChangeSubmitted.bind(this);
@@ -117,8 +118,11 @@ class NewChannelFlow extends React.Component {
member: data2.member
});
+ this.doOnModalExited = () => {
+ browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + data2.channel.name);
+ };
+
this.props.onModalDismissed();
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + data2.channel.name);
}
);
},
@@ -143,6 +147,11 @@ class NewChannelFlow extends React.Component {
}
);
}
+ onModalExited() {
+ if (this.doOnModalExited) {
+ this.doOnModalExited();
+ }
+ }
typeSwitched() {
if (this.state.channelType === 'P') {
this.setState({channelType: 'O'});
@@ -223,6 +232,7 @@ class NewChannelFlow extends React.Component {
serverError={this.state.serverError}
onSubmitChannel={this.doSubmit}
onModalDismissed={this.props.onModalDismissed}
+ onModalExited={this.onModalExited}
onTypeSwitched={this.typeSwitched}
onChangeURLPressed={this.urlChangeRequested}
onDataChanged={this.channelDataChanged}
@@ -233,6 +243,7 @@ class NewChannelFlow extends React.Component {
channelData={channelData}
serverError={this.state.serverError}
onSubmitChannel={this.doSubmit}
+ onModalExited={this.onModalExited}
onModalDismissed={this.props.onModalDismissed}
onTypeSwitched={this.typeSwitched}
onChangeURLPressed={this.urlChangeRequested}
@@ -248,6 +259,7 @@ class NewChannelFlow extends React.Component {
serverError={this.state.serverError}
onModalSubmit={this.urlChangeSubmitted}
onModalDismissed={this.urlChangeDismissed}
+ onModalExited={this.onModalExited}
/>
</span>
);
diff --git a/webapp/components/new_channel_modal.jsx b/webapp/components/new_channel_modal.jsx
index 4122c3bfb..fa52c56a7 100644
--- a/webapp/components/new_channel_modal.jsx
+++ b/webapp/components/new_channel_modal.jsx
@@ -206,9 +206,11 @@ class NewChannelModal extends React.Component {
return (
<span>
<Modal
+ dialogClassName='new-channel__modal'
show={this.props.show}
bsSize='large'
onHide={this.props.onModalDismissed}
+ onExited={this.props.onModalExited}
>
<Modal.Header closeButton={true}>
<Modal.Title>
@@ -382,6 +384,7 @@ NewChannelModal.propTypes = {
serverError: React.PropTypes.node,
onSubmitChannel: React.PropTypes.func.isRequired,
onModalDismissed: React.PropTypes.func.isRequired,
+ onModalExited: React.PropTypes.func.optional,
onTypeSwitched: React.PropTypes.func.isRequired,
onChangeURLPressed: React.PropTypes.func.isRequired,
onDataChanged: React.PropTypes.func.isRequired
diff --git a/webapp/components/password_reset_send_link.jsx b/webapp/components/password_reset_send_link.jsx
index 18741b816..1cd532855 100644
--- a/webapp/components/password_reset_send_link.jsx
+++ b/webapp/components/password_reset_send_link.jsx
@@ -52,14 +52,14 @@ class PasswordResetSendLink extends React.Component {
<div className='reset-form alert alert-success'>
<FormattedHTMLMessage
id='password_send.link'
- defaultMessage='<p>A password reset link has been sent to <b>{email}</b></p>'
+ defaultMessage='If the account exists, a password reset email will be sent to: <br/><b>{email}</b><br/><br/>'
values={{
email
}}
/>
<FormattedMessage
- id={'password_send.checkInbox'}
- defaultMessage={'Please check your inbox.'}
+ id='password_send.checkInbox'
+ defaultMessage='Please check your inbox.'
/>
</div>
)
diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx
index f128245e4..8d50338e0 100644
--- a/webapp/components/search_results.jsx
+++ b/webapp/components/search_results.jsx
@@ -19,7 +19,7 @@ import React from 'react';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
function getStateFromStores() {
- const results = SearchStore.getSearchResults();
+ const results = JSON.parse(JSON.stringify(SearchStore.getSearchResults()));
const channels = new Map();
diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx
index 698d68bba..d9955a136 100644
--- a/webapp/components/search_results_item.jsx
+++ b/webapp/components/search_results_item.jsx
@@ -111,42 +111,132 @@ export default class SearchResultsItem extends React.Component {
compactClass = 'post--compact';
}
- let flag;
- let flagFunc;
- let flagVisible = '';
- let flagTooltip = (
- <Tooltip id='flagTooltip'>
- <FormattedMessage
- id='flag_post.flag'
- defaultMessage='Flag for follow up'
- />
- </Tooltip>
- );
- if (this.props.isFlagged) {
- flagVisible = 'visible';
- flagTooltip = (
+ let message;
+ let flagContent;
+ let rhsControls;
+ if (post.state === Constants.POST_DELETED) {
+ message = (
+ <p>
+ <FormattedMessage
+ id='post_body.deleted'
+ defaultMessage='(message deleted)'
+ />
+ </p>
+ );
+ } else {
+ let flag;
+ let flagFunc;
+ let flagVisible = '';
+ let flagTooltip = (
<Tooltip id='flagTooltip'>
<FormattedMessage
- id='flag_post.unflag'
- defaultMessage='Unflag'
+ id='flag_post.flag'
+ defaultMessage='Flag for follow up'
/>
</Tooltip>
);
- flagFunc = this.unflagPost;
- flag = (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: flagIcon}}
- />
+
+ if (this.props.isFlagged) {
+ flagVisible = 'visible';
+ flagTooltip = (
+ <Tooltip id='flagTooltip'>
+ <FormattedMessage
+ id='flag_post.unflag'
+ defaultMessage='Unflag'
+ />
+ </Tooltip>
+ );
+ flagFunc = this.unflagPost;
+ flag = (
+ <span
+ className='icon'
+ dangerouslySetInnerHTML={{__html: flagIcon}}
+ />
+ );
+ } else {
+ flag = (
+ <span
+ className='icon'
+ dangerouslySetInnerHTML={{__html: flagIcon}}
+ />
+ );
+ flagFunc = this.flagPost;
+ }
+
+ flagContent = (
+ <OverlayTrigger
+ delayShow={Constants.OVERLAY_TIME_DELAY}
+ placement='top'
+ overlay={flagTooltip}
+ >
+ <a
+ href='#'
+ className={'flag-icon__container ' + flagVisible}
+ onClick={flagFunc}
+ >
+ {flag}
+ </a>
+ </OverlayTrigger>
);
- } else {
- flag = (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: flagIcon}}
+
+ rhsControls = (
+ <li className='col__controls'>
+ <a
+ href='#'
+ className='comment-icon__container search-item__comment'
+ onClick={this.handleFocusRHSClick}
+ >
+ <span
+ className='comment-icon'
+ dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
+ />
+ </a>
+ <a
+ onClick={
+ () => {
+ if (Utils.isMobile()) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_SEARCH,
+ results: null
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_SEARCH_TERM,
+ term: null,
+ do_search: false,
+ is_mention_search: false
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_POST_SELECTED,
+ postId: null
+ });
+
+ this.hideSidebar();
+ }
+ this.shrinkSidebar();
+ browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/pl/' + post.id);
+ }
+ }
+ className='search-item__jump'
+ >
+ <FormattedMessage
+ id='search_item.jump'
+ defaultMessage='Jump'
+ />
+ </a>
+ </li>
+ );
+
+ message = (
+ <PostMessageContainer
+ post={post}
+ options={{
+ searchTerm: this.props.term,
+ mentionHighlight: this.props.isMentionSearch
+ }}
/>
);
- flagFunc = this.flagPost;
}
return (
@@ -187,75 +277,12 @@ export default class SearchResultsItem extends React.Component {
minute='2-digit'
/>
</time>
- <OverlayTrigger
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={flagTooltip}
- >
- <a
- href='#'
- className={'flag-icon__container ' + flagVisible}
- onClick={flagFunc}
- >
- {flag}
- </a>
- </OverlayTrigger>
- </li>
- <li className='col__controls'>
- <a
- href='#'
- className='comment-icon__container search-item__comment'
- onClick={this.handleFocusRHSClick}
- >
- <span
- className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
- />
- </a>
- <a
- onClick={
- () => {
- if (Utils.isMobile()) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH_TERM,
- term: null,
- do_search: false,
- is_mention_search: false
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
-
- this.hideSidebar();
- }
- this.shrinkSidebar();
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/pl/' + post.id);
- }
- }
- className='search-item__jump'
- >
- <FormattedMessage
- id='search_item.jump'
- defaultMessage='Jump'
- />
- </a>
+ {flagContent}
</li>
+ {rhsControls}
</ul>
<div className='search-item-snippet'>
- <PostMessageContainer
- post={post}
- options={{
- searchTerm: this.props.term,
- mentionHighlight: this.props.isMentionSearch
- }}
- />
+ {message}
</div>
</div>
</div>
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
index 86026967a..f201adfcf 100644
--- a/webapp/components/sidebar_right_menu.jsx
+++ b/webapp/components/sidebar_right_menu.jsx
@@ -68,6 +68,7 @@ export default class SidebarRightMenu extends React.Component {
getFlagged(e) {
e.preventDefault();
getFlaggedPosts();
+ this.hideSidebars();
}
componentDidMount() {
diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx
index 2d4b3f277..b67179604 100644
--- a/webapp/components/signup/components/signup_email.jsx
+++ b/webapp/components/signup/components/signup_email.jsx
@@ -429,9 +429,11 @@ export default class SignupEmail extends React.Component {
<p>
<FormattedHTMLMessage
id='create_team.agreement'
- defaultMessage="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}."
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
values={{
- siteName: global.window.mm_config.SiteName
+ siteName: global.window.mm_config.SiteName,
+ TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink,
+ PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink
}}
/>
</p>
diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx
index 8c1b1bafb..bc8c073ad 100644
--- a/webapp/components/signup/components/signup_ldap.jsx
+++ b/webapp/components/signup/components/signup_ldap.jsx
@@ -179,9 +179,11 @@ export default class SignupLdap extends React.Component {
<p>
<FormattedHTMLMessage
id='create_team.agreement'
- defaultMessage="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}."
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
values={{
- siteName: global.window.mm_config.SiteName
+ siteName: global.window.mm_config.SiteName,
+ TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink,
+ PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink
}}
/>
</p>
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index d1a03deb5..6118b8d98 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -4,6 +4,7 @@
import Suggestion from './suggestion.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+import SuggestionStore from 'stores/suggestion_store.jsx';
import {autocompleteUsersInChannel} from 'actions/user_actions.jsx';
@@ -106,13 +107,24 @@ export default class AtMentionProvider {
}
componentWillUnmount() {
- clearTimeout(this.timeoutId);
+ this.clearTimeout(this.timeoutId);
+ }
+
+ clearTimeout() {
+ if (this.timeoutId) {
+ clearTimeout(this.timeoutId);
+ this.timeoutId = '';
+
+ return true;
+ }
+
+ return false;
}
handlePretextChanged(suggestionId, pretext) {
- clearTimeout(this.timeoutId);
+ const hadSuggestions = this.clearTimeout(this.timeoutId);
- const captured = (/(?:^|\W)@([a-z0-9\-\._]*)$/i).exec(pretext.toLowerCase());
+ const captured = (/(?:^|\W)@([a-z0-9\-._]*)$/i).exec(pretext.toLowerCase());
if (captured) {
const prefix = captured[1];
@@ -160,5 +172,10 @@ export default class AtMentionProvider {
Constants.AUTOCOMPLETE_TIMEOUT
);
}
+
+ if (hadSuggestions) {
+ // Clear the suggestions since the user has now typed something invalid
+ SuggestionStore.clearSuggestions(suggestionId);
+ }
}
}
diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx
index eeae5ba28..3a8cd65cf 100644
--- a/webapp/components/suggestion/suggestion_box.jsx
+++ b/webapp/components/suggestion/suggestion_box.jsx
@@ -39,7 +39,8 @@ export default class SuggestionBox extends React.Component {
componentWillReceiveProps(nextProps) {
// Clear any suggestions when the SuggestionBox is cleared
if (nextProps.value === '' && this.props.value !== nextProps.value) {
- GlobalActions.emitClearSuggestions(this.suggestionId);
+ // TODO - Find a better way to not "dispatch during dispatch"
+ setTimeout(() => GlobalActions.emitClearSuggestions(this.suggestionId), 1);
}
}
diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx
index 1d749f480..a5281d238 100644
--- a/webapp/components/team_general_tab.jsx
+++ b/webapp/components/team_general_tab.jsx
@@ -5,12 +5,11 @@ import $ from 'jquery';
import SettingItemMin from './setting_item_min.jsx';
import SettingItemMax from './setting_item_max.jsx';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {updateTeam} from 'actions/team_actions.jsx';
const holders = defineMessages({
dirDisabled: {
@@ -131,10 +130,8 @@ class GeneralTab extends React.Component {
var data = this.props.team;
data.allow_open_invite = this.state.allow_open_invite;
- Client.updateTeam(data,
- (team) => {
- TeamStore.saveTeam(team);
- TeamStore.emitChange();
+ updateTeam(data,
+ () => {
this.updateSection('');
},
(err) => {
@@ -170,10 +167,8 @@ class GeneralTab extends React.Component {
var data = this.props.team;
data.display_name = this.state.name;
- Client.updateTeam(data,
- (team) => {
- TeamStore.saveTeam(team);
- TeamStore.emitChange();
+ updateTeam(data,
+ () => {
this.updateSection('');
},
(err) => {
@@ -205,10 +200,8 @@ class GeneralTab extends React.Component {
var data = this.props.team;
data.invite_id = this.state.invite_id;
- Client.updateTeam(data,
- (team) => {
- TeamStore.saveTeam(team);
- TeamStore.emitChange();
+ updateTeam(data,
+ () => {
this.updateSection('');
},
(err) => {
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index e69d917a3..21dbf9699 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -11,7 +11,7 @@ import Constants from 'utils/constants.jsx';
const UserStatuses = Constants.UserStatuses;
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-import {Popover, OverlayTrigger} from 'react-bootstrap';
+import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import React from 'react';
@@ -111,8 +111,19 @@ export default class UserProfile extends React.Component {
defaultMessage='New call unavailable until your existing call ends'
/>
);
+ } else {
+ webrtcMessage = (
+ <FormattedMessage
+ id='user_profile.webrtc.offline'
+ defaultMessage='The user is offline'
+ />
+ );
}
+ const webrtcTooltip = (
+ <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
+ );
+
webrtc = (
<div
className='webrtc__user-profile'
@@ -123,28 +134,18 @@ export default class UserProfile extends React.Component {
onClick={() => this.initWebrtc()}
disabled={!isOnline}
>
- <svg
- id='webrtc-btn'
- className='webrtc__button'
- xmlns='http://www.w3.org/2000/svg'
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={webrtcTooltip}
>
- <circle
- className={circleClass}
- cx='16'
- cy='16'
- r='18'
+ <div
+ id='webrtc-btn'
+ className={'webrtc__button ' + circleClass}
>
- <title>
- {webrtcMessage}
- </title>
- </circle>
- <path
- className='off'
- transform='scale(0.4), translate(17,16)'
- d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
- fill='white'
- />
- </svg>
+ <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
+ </div>
+ </OverlayTrigger>
</a>
</div>
);
diff --git a/webapp/components/user_settings/manage_languages.jsx b/webapp/components/user_settings/manage_languages.jsx
index f4ae79088..4f5eb223d 100644
--- a/webapp/components/user_settings/manage_languages.jsx
+++ b/webapp/components/user_settings/manage_languages.jsx
@@ -3,13 +3,12 @@
import SettingItemMax from '../setting_item_max.jsx';
-import Client from 'client/web_client.jsx';
import * as I18n from 'i18n/i18n.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
+import {updateUser} from 'actions/user_actions.jsx';
import React from 'react';
export default class ManageLanguage extends React.Component {
@@ -42,7 +41,7 @@ export default class ManageLanguage extends React.Component {
this.submitUser(user);
}
submitUser(user) {
- Client.updateUser(user, Constants.UserUpdateEvents.LANGUAGE,
+ updateUser(user, Constants.UserUpdateEvents.LANGUAGE,
() => {
GlobalActions.newLocalizationSelected(user.locale);
},
diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx
index e794c4d4b..805650608 100644
--- a/webapp/components/user_settings/user_settings_general.jsx
+++ b/webapp/components/user_settings/user_settings_general.jsx
@@ -15,6 +15,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
+import {updateUser} from 'actions/user_actions.jsx';
const holders = defineMessages({
usernameReserved: {
@@ -187,7 +188,7 @@ class UserSettingsGeneralTab extends React.Component {
}
submitUser(user, type, emailUpdated) {
- Client.updateUser(user, type,
+ updateUser(user, type,
() => {
this.updateSection('');
AsyncClient.getMe();
@@ -461,7 +462,7 @@ class UserSettingsGeneralTab extends React.Component {
inputs.push(
<div
key='oauthEmailInfo'
- className='form-group'
+ className='padding-bottom'
>
<div className='setting-list__hint'>
<FormattedMessage
@@ -479,7 +480,7 @@ class UserSettingsGeneralTab extends React.Component {
inputs.push(
<div
key='oauthEmailInfo'
- className='form-group'
+ className='padding-bottom'
>
<div className='setting-list__hint'>
<FormattedMessage
diff --git a/webapp/components/user_settings/user_settings_modal.jsx b/webapp/components/user_settings/user_settings_modal.jsx
index 9112f8711..c1194ed78 100644
--- a/webapp/components/user_settings/user_settings_modal.jsx
+++ b/webapp/components/user_settings/user_settings_modal.jsx
@@ -104,7 +104,6 @@ class UserSettingsModal extends React.Component {
}
this.props.onModalDismissed();
- return;
}
// called after the dialog is fully hidden and faded out
@@ -251,7 +250,6 @@ class UserSettingsModal extends React.Component {
setRequireConfirm={
(requireConfirm) => {
this.requireConfirm = requireConfirm;
- return;
}
}
/>
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index 0cee3dfca..5f231e499 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -289,7 +289,7 @@ export default class SecurityTab extends React.Component {
<span>
<FormattedMessage
id='user.settings.mfa.addHelpQr'
- defaultMessage='Please scan the QR code with the Google Authenticator app on your smartphone and fill in the token with one provided by the app. If you are unable to scan the code, you can maunally enter the secret provided.'
+ defaultMessage='Please scan the QR code with the Google Authenticator app on your smartphone and fill in the token with one provided by the app. If you are unable to scan the code, you can manually enter the secret provided.'
/>
</span>
);
@@ -473,6 +473,20 @@ export default class SecurityTab extends React.Component {
</div>
</div>
);
+ } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
+ inputs.push(
+ <div
+ key='oauthEmailInfo'
+ className='form-group'
+ >
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.security.passwordSamlCantUpdate'
+ defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so through your login provider.'
+ />
+ </div>
+ </div>
+ );
}
updateSectionStatus = function resetSection(e) {
@@ -533,7 +547,7 @@ export default class SecurityTab extends React.Component {
describe = (
<FormattedMessage
id='user.settings.security.loginGitlab'
- defaultMessage='Login done through Gitlab'
+ defaultMessage='Login done through GitLab'
/>
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
@@ -543,6 +557,13 @@ export default class SecurityTab extends React.Component {
defaultMessage='Login done through AD/LDAP'
/>
);
+ } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.loginSaml'
+ defaultMessage='Login done through SAML'
+ />
+ );
}
updateSectionStatus = function updateSection() {
diff --git a/webapp/components/youtube_video.jsx b/webapp/components/youtube_video.jsx
index 93ea4f946..908a2c74e 100644
--- a/webapp/components/youtube_video.jsx
+++ b/webapp/components/youtube_video.jsx
@@ -5,7 +5,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import WebClient from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
-const ytRegex = /(?:http|https):\/\/(?:www\.|m\.)?(?:(?:youtube\.com\/(?:(?:v\/)|(?:(?:watch|embed\/watch)(?:\/|.*v=))|(?:embed\/)|(?:user\/[^\/]+\/u\/[0-9]\/)))|(?:youtu\.be\/))([^#&\?]*)/;
+const ytRegex = /(?:http|https):\/\/(?:www\.|m\.)?(?:(?:youtube\.com\/(?:(?:v\/)|(?:(?:watch|embed\/watch)(?:\/|.*v=))|(?:embed\/)|(?:user\/[^/]+\/u\/[0-9]\/)))|(?:youtu\.be\/))([^#&?]*)/;
import React from 'react';
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 220d4e2f9..d0df67e68 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -73,7 +73,7 @@
"add_emoji.header": "Add",
"add_emoji.image": "Image",
"add_emoji.image.button": "Select",
- "add_emoji.image.help": "Choose the image for your emoji. The image can be a gif, png, or jpeg file with a max size of 64 KB and dimensions up to 128 by 128 pixels.",
+ "add_emoji.image.help": "Choose the image for your emoji. The image can be a gif, png, or jpeg file with a max size of 1 MB. Dimensions will automatically resize to fit 128 by 128 pixels but keeping aspect ratio.",
"add_emoji.imageRequired": "An image is required for the emoji",
"add_emoji.name": "Name",
"add_emoji.name.help": "Choose a name for your emoji made of up to 64 characters consisting of lowercase letters, numbers, and the symbols '-' and '_'.",
@@ -137,6 +137,7 @@
"add_outgoing_webhook.triggerWordsTriggerWhenFullWord": "First word matches a trigger word exactly",
"add_outgoing_webhook.triggerWordsTriggerWhenStartsWith": "First word starts with a trigger word",
"admin.advance.cluster": "High Availability (Beta)",
+ "admin.advance.metrics": "Performance Monitoring (Beta)",
"admin.audits.reload": "Reload User Activity Logs",
"admin.audits.title": "User Activity Logs",
"admin.authentication.email": "Email Auth",
@@ -145,6 +146,11 @@
"admin.authentication.oauth": "OAuth 2.0",
"admin.authentication.saml": "SAML",
"admin.banner.heading": "Note:",
+ "admin.metrics.enableTitle": "Enable Performance Monitoring:",
+ "admin.metrics.enableDescription": "When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target=\"_blank\">documentation</a> to learn more about configuring performance monitoring for Mattermost.",
+ "admin.metrics.listenAddressTitle": "Listen Address:",
+ "admin.metrics.listenAddressEx": "Ex \":8067\"",
+ "admin.metrics.listenAddressDesc": "The address the server will listen on to expose performance metrics.",
"admin.cluster.enableDescription": "When true, Mattermost will run in High Availability mode. Please see <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a> to learn more about configuring High Availability for Mattermost.",
"admin.cluster.enableTitle": "Enable High Availability Mode:",
"admin.cluster.interNodeListenAddressDesc": "The address the server will listen on for communicating with other servers.",
@@ -298,6 +304,8 @@
"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). 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.availableLocalesNoResults": "No results found",
+ "admin.general.localization.availableLocalesNotPresent": "The default client language must be included in the available list",
"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:",
@@ -348,12 +356,16 @@
"admin.image.amazonS3BucketDescription": "Name you selected for your S3 bucket in AWS.",
"admin.image.amazonS3BucketExample": "E.g.: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
+ "admin.image.amazonS3EndpointDescription": "Hostname of your S3 Compatible Storage provider. Defaults to `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointTitle": "Amazon S3 Endpoint:",
"admin.image.amazonS3IdDescription": "Obtain this credential from your Amazon EC2 administrator.",
"admin.image.amazonS3IdExample": "E.g.: \"AKIADTOVBGERKLCBV\"",
"admin.image.amazonS3IdTitle": "Amazon S3 Access Key ID:",
"admin.image.amazonS3RegionDescription": "AWS region you selected for creating your S3 bucket.",
"admin.image.amazonS3RegionExample": "E.g.: \"us-east-1\"",
"admin.image.amazonS3RegionTitle": "Amazon S3 Region:",
+ "admin.image.amazonS3SSLDescription": "When false, allow insecure connections to Amazon S3. Defaults to secure connections only.",
+ "admin.image.amazonS3SSLTitle": "Enable Secure Amazon S3 Connections:",
"admin.image.amazonS3SecretDescription": "Obtain this credential from your Amazon EC2 administrator.",
"admin.image.amazonS3SecretExample": "E.g.: \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.image.amazonS3SecretTitle": "Amazon S3 Secret Access Key:",
@@ -638,6 +650,8 @@
"admin.service.corsTitle": "Enable cross-origin requests from:",
"admin.service.developerDesc": "When true, Javascript errors are shown in a red bar at the top of the user interface. Not recommended for use in production. ",
"admin.service.developerTitle": "Enable Developer Mode: ",
+ "admin.service.forward80To443": "Forward port 80 to 443:",
+ "admin.service.forward80To443Description": "Forwards all insecure traffic from port 80 to secure port 443",
"admin.service.googleDescription": "Set this key to enable the display of titles for embedded YouTube video previews. Without the key, YouTube previews will still be created based on hyperlinks appearing in messages or comments but they will not show the video title. View a <a href=\"https://www.youtube.com/watch?v=Im69kzhpR3I\" target=\"_blank\">Google Developers Tutorial</a> for instructions on how to obtain a key.",
"admin.service.googleExample": "E.g.: \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"",
"admin.service.googleTitle": "Google API Key:",
@@ -647,6 +661,8 @@
"admin.service.insecureTlsTitle": "Enable Insecure Outgoing Connections: ",
"admin.service.integrationAdmin": "Restrict managing integrations to Admins:",
"admin.service.integrationAdminDesc": "When true, webhooks and slash commands can only be created, edited and viewed by Team and System Admins, and OAuth 2.0 applications by System Admins. Integrations are available to all users after they have been created by the Admin.",
+ "admin.service.letsEncryptCertificateCacheFile": "Let's Encrypt Certificate Cache File:",
+ "admin.service.letsEncryptCertificateCacheFileDescription": "Certificates retrieved and other data about the Let's Encrypt service will be stored in this file.",
"admin.service.listenAddress": "Listen Address:",
"admin.service.listenDescription": "The address and port to which to bind and listen. Specifying \":8065\" will bind to all network interfaces. Specifying \"127.0.0.1:8065\" will only bind to the network interface having that IP address. If you choose a port of a lower level (called \"system ports\" or \"well-known ports\", in the range of 0-1023), you must have permissions to bind to that port. On Linux you can use: \"sudo setcap cap_net_bind_service=+ep ./bin/platform\" to allow Mattermost to bind to well-known ports.",
"admin.service.listenExample": "E.g.: \":8065\"",
@@ -658,6 +674,8 @@
"admin.service.outWebhooksTitle": "Enable Outgoing Webhooks: ",
"admin.service.overrideDescription": "When true, webhooks, slash commands and other integrations, such as <a href=\"https://docs.mattermost.com/integrations/zapier.html\" target=\"_blank\">Zapier</a>, will be allowed to change the username they are posting as. Note: Combined with allowing integrations to override profile picture icons, users may be able to perform phishing attacks by attempting to impersonate other users.",
"admin.service.overrideTitle": "Enable integrations to override usernames:",
+ "admin.service.readTimeout": "Read Timeout:",
+ "admin.service.readTimeoutDescription": "Maximum time allowed from when the connection is accepted to when the request body is fully read.",
"admin.service.securityDesc": "When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.",
"admin.service.securityTitle": "Enable Security Alerts: ",
"admin.service.segmentDescription": "Segment.com is an online service that can be optionally used to track detailed system statistics. You can obtain a key by signing-up for a free account at Segment.com.",
@@ -673,15 +691,24 @@
"admin.service.ssoSessionDaysDesc": "The number of days from the last time a user entered their credentials to the expiry of the user's session. If the authentication method is SAML or GitLab, the user may automatically be logged back in to Mattermost if they are already logged in to SAML or GitLab. After changing this setting, the setting will take effect after the next time the user enters their credentials.",
"admin.service.testingDescription": "When true, /loadtest slash command is enabled to load test accounts, data and text formatting. Changing this requires a server restart before taking effect.",
"admin.service.testingTitle": "Enable Testing Commands: ",
+ "admin.service.tlsCertFile": "TLS Certificate File:",
+ "admin.service.tlsCertFileDescription": "The certificate file to use.",
+ "admin.service.tlsKeyFile": "TLS Key File:",
+ "admin.service.tlsKeyFileDescription": "The private key file to use.",
+ "admin.service.useLetsEncrypt": "Use Let's Encrypt:",
+ "admin.service.useLetsEncryptDescription": "Enable the automatic retreval of certificates from the Let's Encrypt. The certificate will be retrieved when a client attempts to connect from a new domain. This will work with multiple domains.",
"admin.service.webSessionDays": "Session length AD/LDAP and email (days):",
"admin.service.webSessionDaysDesc": "The number of days from the last time a user entered their credentials to the expiry of the user's session. After changing this setting, the new session length will take effect after the next time the user enters their credentials.",
"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.service.writeTimeout": "Write Timeout:",
+ "admin.service.writeTimeoutDescription": "If using HTTP (insecure), this is the maximum time allowed from the end of reading the request headers until the response is written. If using HTTPS, it is the total time from when the connection is accepted until the response is written.",
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
"admin.sidebar.advanced": "Advanced",
"admin.sidebar.audits": "Compliance and Auditing",
"admin.sidebar.authentication": "Authentication",
"admin.sidebar.cluster": "High Availability (Beta)",
+ "admin.sidebar.metrics": "Performance Monitoring (Beta)",
"admin.sidebar.compliance": "Compliance",
"admin.sidebar.configuration": "Configuration",
"admin.sidebar.connections": "Connections",
@@ -780,6 +807,9 @@
"admin.team.maxChannelsDescription": "Maximum total number of channels per team, including both active and deleted channels.",
"admin.team.maxChannelsExample": "Ex \"100\"",
"admin.team.maxChannelsTitle": "Max Channels Per Team:",
+ "admin.team.maxNotificationsPerChannelTitle" : "Max Notifications Per Channel:",
+ "admin.team.maxNotificationsPerChannelExample" : "Ex \"1000\"",
+ "admin.team.maxNotificationsPerChannelDescription" : "Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.",
"admin.team.maxUsersDescription": "Maximum total number of users per team, including both active and inactive users.",
"admin.team.maxUsersExample": "E.g.: \"25\"",
"admin.team.maxUsersTitle": "Max Users Per Team:",
@@ -957,6 +987,7 @@
"authorize.title": "<strong>{appName}</strong> would like to connect to your <strong>Mattermost</strong> user account",
"backstage_list.search": "Search",
"backstage_navbar.backToMattermost": "Back to {siteName}",
+ "backstage_sidebar.emoji": "Custom Emoji",
"backstage_sidebar.integrations": "Integrations",
"backstage_sidebar.integrations.commands": "Slash Commands",
"backstage_sidebar.integrations.incoming_webhooks": "Incoming Webhooks",
@@ -971,6 +1002,8 @@
"change_url.longer": "Must be longer than two characters",
"change_url.noUnderscore": "Can not contain two underscores in a row.",
"change_url.startWithLetter": "Must start with a letter or number",
+ "channelHeader.addToFavorites": "Add to Favorites",
+ "channelHeader.removeFromFavorites": "Remove from Favorites",
"channel_flow.alreadyExist": "A channel with that URL already exists",
"channel_flow.changeUrlDescription": "Some characters are not allowed in URLs and may be removed.",
"channel_flow.changeUrlTitle": "Change {term} URL",
@@ -997,6 +1030,7 @@
"channel_header.viewInfo": "View Info",
"channel_header.viewMembers": "View Members",
"channel_header.webrtc.call": "Start Video Call",
+ "channel_header.webrtc.offline": "The user is offline",
"channel_header.webrtc.unavailable": "New call unavailable until your existing call ends",
"channel_info.about": "About",
"channel_info.close": "Close",
@@ -1097,6 +1131,7 @@
"claim.oauth_to_email.switchTo": "Switch {type} to email and password",
"claim.oauth_to_email.title": "Switch {type} Account to Email",
"confirm_modal.cancel": "Cancel",
+ "connecting_screen": "Connecting",
"create_comment.addComment": "Add a comment...",
"create_comment.comment": "Add Comment",
"create_comment.commentLength": "Comment length must be less than {max} characters.",
@@ -1108,7 +1143,7 @@
"create_post.shortcutsNotSupported": "Keyboard shortcuts are not supported on your device.",
"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}.",
+ "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href={TermsOfServiceLink}>Terms of Service</a> and <a href={PrivacyPolicyLink}>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
"create_team.display_name.back": "Back to previous step",
"create_team.display_name.charLength": "Name must be 2 or more characters up to a maximum of 15",
"create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.",
@@ -1116,7 +1151,7 @@
"create_team.display_name.required": "This field is required",
"create_team.display_name.teamName": "Team Name",
"create_team.team_url.back": "Back to previous step",
- "create_team.team_url.charLength": "Name must be 2 or more characters up to a maximum of 15",
+ "create_team.team_url.charLength": "Name must be {min} or more characters up to a maximum of {max}",
"create_team.team_url.creatingTeam": "Creating team...",
"create_team.team_url.finish": "Finish",
"create_team.team_url.hint": "<li>Short and memorable is best</li><li>Use lowercase letters, numbers and dashes</li><li>Must start with a letter and can't end in a dash</li>",
@@ -1156,6 +1191,7 @@
"edit_channel_purpose_modal.save": "Save",
"edit_channel_purpose_modal.title1": "Edit Purpose",
"edit_channel_purpose_modal.title2": "Edit Purpose for ",
+ "edit_command.save": "Update",
"edit_post.cancel": "Cancel",
"edit_post.edit": "Edit {title}",
"edit_post.editPost": "Edit the post...",
@@ -1177,6 +1213,7 @@
"emoji_list.creator": "Creator",
"emoji_list.delete": "Delete",
"emoji_list.empty": "No Custom Emoji Found",
+ "emoji_list.header": "Custom Emoji",
"emoji_list.help": "Custom emoji are available to everyone on your server. Type ':' in a message box to bring up the emoji selection menu. Other users may need to refresh the page before new emojis appear.",
"emoji_list.help2": "Tip: If you add #, ##, or ### as the first character on a new line containing emoji, you can use larger sized emoji. To try it out, send a message such as: '# :smile:'.",
"emoji_list.image": "Image",
@@ -1208,6 +1245,7 @@
"filtered_user_list.countTotalPage": "{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}} of {total} total",
"filtered_user_list.member": "Member",
"filtered_user_list.search": "Press enter to search",
+ "filtered_user_list.searchButton": "Search",
"filtered_user_list.show": "Filter:",
"filtered_user_list.team_only": "Members of this Team",
"find_team.email": "Email",
@@ -1361,6 +1399,7 @@
"installed_integrations.content_type": "Content-Type: {contentType}",
"installed_integrations.creation": "Created by {creator} on {createAt, date, full}",
"installed_integrations.delete": "Delete",
+ "installed_integrations.edit": "Edit",
"installed_integrations.hideSecret": "Hide Secret",
"installed_integrations.regenSecret": "Regenerate Secret",
"installed_integrations.regenToken": "Regenerate Token",
@@ -1398,6 +1437,7 @@
"integrations.command.description": "Slash commands send events to external integrations",
"integrations.command.title": "Slash Command",
"integrations.done": "Done",
+ "integrations.edit": "Edit",
"integrations.header": "Integrations",
"integrations.incomingWebhook.description": "Incoming webhooks allow external integrations to send messages",
"integrations.incomingWebhook.title": "Incoming Webhook",
@@ -1410,7 +1450,7 @@
"intro_messages.anyMember": " Any member can join and read this channel.",
"intro_messages.beginning": "Beginning of {name}",
"intro_messages.channel": "channel",
- "intro_messages.creator": "This is the start of the <strong>{name}</strong> {type}, created by <strong>{creator}</strong> on <strong>{date}</strong>",
+ "intro_messages.creator": "This is the start of the {name} {type}, created by {creator} on {date}.",
"intro_messages.default": "<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>",
"intro_messages.group": "private group",
"intro_messages.invite": "Invite others to this {type}",
@@ -1418,6 +1458,7 @@
"intro_messages.noCreator": "This is the start of the {name} {type}, created on {date}.",
"intro_messages.offTopic": "<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>",
"intro_messages.onlyInvited": " Only invited members can see this private group.",
+ "intro_messages.purpose": " This {type}'s purpose is: {purpose}.",
"intro_messages.setHeader": "Set a Header",
"intro_messages.teammate": "This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.",
"invite_member.addAnother": "Add another",
@@ -1542,6 +1583,7 @@
"navbar_dropdown.teamSettings": "Team Settings",
"navbar_dropdown.viewMembers": "View Members",
"notification.dm": "Direct Message",
+ "passwordRequirements": "Password Requirements:",
"password_form.change": "Change my password",
"password_form.click": "Click <a href={url}>here</a> to log in.",
"password_form.enter": "Enter a new password for your {siteName} account.",
@@ -1553,7 +1595,7 @@
"password_send.description": "To reset your password, enter the email address you used to sign up",
"password_send.email": "Email",
"password_send.error": "Please enter a valid email address.",
- "password_send.link": "<p>A password reset link has been sent to <b>{email}</b></p>",
+ "password_send.link": "If the account exists, a password reset email will be sent to: <br/><b>{email}</b><br/><br/>",
"password_send.reset": "Reset my password",
"password_send.title": "Password Reset",
"pdf_preview.max_pages": "Download to read more pages",
@@ -1600,11 +1642,14 @@
"rhs_comment.mobile.flag": "Flag",
"rhs_comment.mobile.unflag": "Unflag",
"rhs_comment.permalink": "Permalink",
+ "rhs_header.backToCallTooltip": "Back to Call",
"rhs_header.backToFlaggedTooltip": "Back to Flagged Posts",
"rhs_header.backToResultsTooltip": "Back to Search Results",
"rhs_header.closeSidebarTooltip": "Close Sidebar",
+ "rhs_header.closeTooltip": "Close Sidebar",
"rhs_header.details": "Message Details",
"rhs_header.expandSidebarTooltip": "Expand Sidebar",
+ "rhs_header.expandTooltip": "Shrink Sidebar",
"rhs_header.shrinkSidebarTooltip": "Shrink Sidebar",
"rhs_root.del": "Delete",
"rhs_root.direct": "Direct Message",
@@ -1786,6 +1831,10 @@
"tutorial_tip.ok": "Okay",
"tutorial_tip.out": "Opt out of these tips.",
"tutorial_tip.seen": "Seen this before? ",
+ "update_command.cancel": "Cancel",
+ "update_command.confirm": "Edit Slash Command",
+ "update_command.question": "Your changes may break the existing slash command. Are you sure you would like to update it?",
+ "update_command.update": "Update",
"upload_overlay.info": "Drop a file to upload it.",
"user.settings.advance.embed_preview": "Show experimental previews of link content, when available",
"user.settings.advance.embed_toggle": "Show toggle for all embed previews",
@@ -1916,7 +1965,7 @@
"user.settings.languages.promote": "Select which language Mattermost displays in the user interface.<br /><br />Would like to help with translations? Join the <a href='http://translate.mattermost.com/' target='_blank'>Mattermost Translation Server</a> to contribute.",
"user.settings.mfa.add": "Add MFA to your account",
"user.settings.mfa.addHelp": "You can require a smartphone-based token, in addition to your password, to sign into Mattermost.<br/><br/>To enable, download Google Authenticator from <a target='_blank' href='https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8'>iTunes</a> or <a target='_blank' href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en'>Google Play</a> for your phone, then<br/><br/>1. Click the <strong>Add MFA to your account</strong> button above.<br/>2. Use Google Authenticator to scan the QR code that appears or type in the secret manually.<br/>3. Type in the Token generated by Google Authenticator and click <strong>Save</strong>.<br/><br/>When logging in, you will be asked to enter a token from Google Authenticator in addition to your regular credentials.",
- "user.settings.mfa.addHelpQr": "Please scan the QR code with the Google Authenticator app on your smartphone and fill in the token with one provided by the app. If you are unable to scan the code, you can maunally enter the secret provided.",
+ "user.settings.mfa.addHelpQr": "Please scan the QR code with the Google Authenticator app on your smartphone and fill in the token with one provided by the app. If you are unable to scan the code, you can manually enter the secret provided.",
"user.settings.mfa.enterToken": "Token (numbers only)",
"user.settings.mfa.qrCode": "Bar Code",
"user.settings.mfa.remove": "Remove MFA from your account",
@@ -1976,6 +2025,7 @@
"user.settings.notifications.on": "On",
"user.settings.notifications.onlyMentions": "Only for mentions and direct messages",
"user.settings.notifications.push": "Mobile push notifications",
+ "user.settings.notifications.push_notification.status": "Trigger push notifications when",
"user.settings.notifications.sensitiveName": "Your case sensitive first name \"{first_name}\"",
"user.settings.notifications.sensitiveUsername": "Your non-case sensitive username \"{username}\"",
"user.settings.notifications.sensitiveWords": "Other non-case sensitive words, separated by commas:",
@@ -1985,6 +2035,7 @@
"user.settings.notifications.title": "Notification Settings",
"user.settings.notifications.usernameMention": "Your username mentioned \"@{username}\"",
"user.settings.notifications.wordsTrigger": "Words that trigger mentions",
+ "user.settings.push_notification.allActivity": "For all activity",
"user.settings.push_notification.allActivityAway": "For all activity when away or offline",
"user.settings.push_notification.allActivityOffline": "For all activity when offline",
"user.settings.push_notification.allActivityOnline": "For all activity when online, away or offline",
@@ -1995,6 +2046,7 @@
"user.settings.push_notification.off": "Off",
"user.settings.push_notification.offline": "Offline",
"user.settings.push_notification.online": "Online, away or offline",
+ "user.settings.push_notification.onlyMentions": "For mentions and direct messages",
"user.settings.push_notification.onlyMentionsAway": "For mentions and direct messages when away or offline",
"user.settings.push_notification.onlyMentionsOffline": "For mentions and direct messages when offline",
"user.settings.push_notification.onlyMentionsOnline": "For mentions and direct messages when online, away or offline",
@@ -2012,6 +2064,7 @@
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Login done through GitLab",
"user.settings.security.loginLdap": "Login done through AD/LDAP",
+ "user.settings.security.loginSaml": "Login done through SAML",
"user.settings.security.logoutActiveSessions": "View and Logout of Active Sessions",
"user.settings.security.method": "Sign-in Method",
"user.settings.security.newPassword": "New Password",
@@ -2042,6 +2095,7 @@
"user.settings.security.passwordLdapCantUpdate": "Login occurs through AD/LDAP. Password cannot be updated.",
"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.passwordSamlCantUpdate": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.",
"user.settings.security.retypePassword": "Retype New Password",
"user.settings.security.saml": "SAML",
"user.settings.security.switchEmail": "Switch to using email and password",
@@ -2054,6 +2108,7 @@
"user.settings.security.viewHistory": "View Access History",
"user_list.notFound": "No users found",
"user_profile.webrtc.call": "Start Video Call",
+ "user_profile.webrtc.offline": "The user is offline",
"user_profile.webrtc.unavailable": "New call unavailable until your existing call ends",
"view_image.loading": "Loading ",
"view_image_popover.download": "Download",
diff --git a/webapp/i18n/i18n.jsx b/webapp/i18n/i18n.jsx
index 0513f1154..7b630c6f4 100644
--- a/webapp/i18n/i18n.jsx
+++ b/webapp/i18n/i18n.jsx
@@ -1,16 +1,16 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-const de = require('!!file?name=i18n/[name].[hash].[ext]!./de.json');
-const es = require('!!file?name=i18n/[name].[hash].[ext]!./es.json');
-const fr = require('!!file?name=i18n/[name].[hash].[ext]!./fr.json');
-const ja = require('!!file?name=i18n/[name].[hash].[ext]!./ja.json');
-const ko = require('!!file?name=i18n/[name].[hash].[ext]!./ko.json');
-const nl = require('!!file?name=i18n/[name].[hash].[ext]!./nl.json');
-const pt_BR = require('!!file?name=i18n/[name].[hash].[ext]!./pt-BR.json'); //eslint-disable-line camelcase
-const ru = require('!!file?name=i18n/[name].[hash].[ext]!./ru.json');
-const zh_TW = require('!!file?name=i18n/[name].[hash].[ext]!./zh_TW.json'); //eslint-disable-line camelcase
-const zh_CN = require('!!file?name=i18n/[name].[hash].[ext]!./zh_CN.json'); //eslint-disable-line camelcase
+const de = require('!!file-loader?name=i18n/[name].[hash].[ext]!./de.json');
+const es = require('!!file-loader?name=i18n/[name].[hash].[ext]!./es.json');
+const fr = require('!!file-loader?name=i18n/[name].[hash].[ext]!./fr.json');
+const ja = require('!!file-loader?name=i18n/[name].[hash].[ext]!./ja.json');
+const ko = require('!!file-loader?name=i18n/[name].[hash].[ext]!./ko.json');
+const nl = require('!!file-loader?name=i18n/[name].[hash].[ext]!./nl.json');
+const pt_BR = require('!!file-loader?name=i18n/[name].[hash].[ext]!./pt-BR.json'); //eslint-disable-line camelcase
+const ru = require('!!file-loader?name=i18n/[name].[hash].[ext]!./ru.json');
+const zh_TW = require('!!file-loader?name=i18n/[name].[hash].[ext]!./zh_TW.json'); //eslint-disable-line camelcase
+const zh_CN = require('!!file-loader?name=i18n/[name].[hash].[ext]!./zh_CN.json'); //eslint-disable-line camelcase
import {addLocaleData} from 'react-intl';
import deLocaleData from 'react-intl/locale-data/de';
diff --git a/webapp/package.json b/webapp/package.json
index 338829559..46838718a 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -3,16 +3,16 @@
"version": "0.0.1",
"private": true,
"dependencies": {
- "autolinker": "1.1.0",
+ "autolinker": "1.2.1",
"bootstrap": "3.3.7",
- "bootstrap-colorpicker": "2.3.5",
- "chart.js": "2.3.0",
+ "bootstrap-colorpicker": "2.3.6",
+ "chart.js": "2.4.0",
"compass-mixins": "0.12.10",
"fastclick": "1.0.6",
- "flux": "2.1.1",
- "font-awesome": "4.6.3",
- "highlight.js": "9.7.0",
- "inobounce": "0.1.3",
+ "flux": "3.1.0",
+ "font-awesome": "4.7.0",
+ "highlight.js": "9.8.0",
+ "inobounce": "0.1.4",
"intl": "1.2.5",
"jasny-bootstrap": "3.1.3",
"jquery": "3.1.1",
@@ -20,58 +20,58 @@
"marked": "mattermost/marked#69736482dbad685c398a5eec33a59b5ab06057ac",
"match-at": "0.1.0",
"object-assign": "4.1.0",
- "pdfjs-dist": "1.5.488",
- "perfect-scrollbar": "0.6.12",
- "react": "15.3.2",
- "react-addons-pure-render-mixin": "15.3.2",
- "react-bootstrap": "0.30.3",
+ "pdfjs-dist": "1.6.319",
+ "perfect-scrollbar": "0.6.14",
+ "react": "15.4.0",
+ "react-addons-pure-render-mixin": "15.4.0",
+ "react-bootstrap": "0.30.6",
"react-custom-scrollbars": "4.0.0",
- "react-dom": "15.3.2",
+ "react-dom": "15.4.0",
"react-intl": "2.1.5",
"react-router": "2.8.1",
"react-select": "1.0.0-rc.2",
"superagent": "2.3.0",
"twemoji": "2.2.0",
- "velocity-animate": "1.2.3",
- "webrtc-adapter": "2.0.3",
+ "velocity-animate": "1.3.1",
+ "webrtc-adapter": "2.0.8",
"xregexp": "3.1.1"
},
"devDependencies": {
- "babel-core": "6.14.0",
- "babel-eslint": "6.1.2",
- "babel-loader": "6.2.5",
- "babel-plugin-transform-runtime": "6.12.0",
- "babel-polyfill": "6.13.0",
- "babel-preset-es2015": "6.14.0",
- "babel-preset-react": "6.11.1",
- "babel-preset-stage-0": "6.5.0",
- "copy-webpack-plugin": "3.0.1",
- "cross-env": "3.0.0",
+ "babel-core": "6.18.2",
+ "babel-eslint": "7.1.0",
+ "babel-loader": "6.2.7",
+ "babel-plugin-transform-runtime": "6.15.0",
+ "babel-polyfill": "6.16.0",
+ "babel-preset-es2015": "6.18.0",
+ "babel-preset-react": "6.16.0",
+ "babel-preset-stage-0": "6.16.0",
+ "copy-webpack-plugin": "4.0.1",
+ "cross-env": "3.1.3",
"css-loader": "0.25.0",
- "eslint": "3.5.0",
- "eslint-plugin-react": "6.3.0",
+ "eslint": "3.10.2",
+ "eslint-plugin-react": "6.7.1",
"exports-loader": "0.6.3",
"extract-text-webpack-plugin": "1.0.1",
"file-loader": "0.9.0",
"html-loader": "0.4.4",
- "html-webpack-plugin": "2.22.0",
+ "html-webpack-plugin": "2.24.1",
"image-webpack-loader": "2.0.0",
"imports-loader": "0.6.5",
"jquery-deferred": "0.3.1",
- "jsdom": "9.5.0",
+ "jsdom": "9.8.3",
"jsdom-global": "2.1.0",
"json-loader": "0.5.4",
- "mocha": "3.0.2",
+ "mocha": "3.1.2",
"mocha-jsdom": "1.1.0",
- "mocha-webpack": "0.6.0",
- "node-sass": "3.8.0",
+ "mocha-webpack": "0.7.0",
+ "node-sass": "3.13.0",
"raw-loader": "0.5.1",
- "react-addons-test-utils": "15.3.2",
+ "react-addons-test-utils": "15.4.0",
"sass-loader": "4.0.2",
"style-loader": "0.13.1",
"url-loader": "0.5.7",
- "webpack": "2.1.0-beta.25",
- "webpack-node-externals": "1.4.3"
+ "webpack": "2.1.0-beta.27",
+ "webpack-node-externals": "1.5.4"
},
"scripts": {
"check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .",
diff --git a/webapp/routes/route_admin_console.jsx b/webapp/routes/route_admin_console.jsx
index e3e22ed68..a67cb3e83 100644
--- a/webapp/routes/route_admin_console.jsx
+++ b/webapp/routes/route_admin_console.jsx
@@ -18,6 +18,7 @@ import OAuthSettings from 'components/admin_console/oauth_settings.jsx';
import LdapSettings from 'components/admin_console/ldap_settings.jsx';
import SamlSettings from 'components/admin_console/saml_settings.jsx';
import ClusterSettings from 'components/admin_console/cluster_settings.jsx';
+import MetricsSettings from 'components/admin_console/metrics_settings.jsx';
import SignupSettings from 'components/admin_console/signup_settings.jsx';
import PasswordSettings from 'components/admin_console/password_settings.jsx';
import PublicLinkSettings from 'components/admin_console/public_link_settings.jsx';
@@ -201,6 +202,10 @@ export default (
path='cluster'
component={ClusterSettings}
/>
+ <Route
+ path='metrics'
+ component={MetricsSettings}
+ />
</Route>
<Route path='team'>
<Redirect
diff --git a/webapp/routes/route_integrations.jsx b/webapp/routes/route_integrations.jsx
index 0feb13bb7..7a4af7e7a 100644
--- a/webapp/routes/route_integrations.jsx
+++ b/webapp/routes/route_integrations.jsx
@@ -66,6 +66,12 @@ export default {
}
},
{
+ path: 'edit',
+ getComponents: (location, callback) => {
+ System.import('components/integrations/components/edit_command.jsx').then(RouteUtils.importComponentSuccess(callback));
+ }
+ },
+ {
path: 'confirm',
getComponents: (location, callback) => {
System.import('components/integrations/components/confirm_integration.jsx').then(RouteUtils.importComponentSuccess(callback));
diff --git a/webapp/sass/components/_emoticons.scss b/webapp/sass/components/_emoticons.scss
index 80a800863..0e6ce6041 100644
--- a/webapp/sass/components/_emoticons.scss
+++ b/webapp/sass/components/_emoticons.scss
@@ -1,11 +1,14 @@
@charset 'UTF-8';
.emoticon {
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
background-size: contain;
display: inline-block;
- height: 1.5em;
- margin-bottom: .25em;
- width: 1.5em;
+ font-size: 19px;
+ height: 1em;
+ vertical-align: text-top;
+ width: 1em;
}
.emoticon-suggestion {
diff --git a/webapp/sass/components/_inputs.scss b/webapp/sass/components/_inputs.scss
index c34d0d2d4..75e5ff9e6 100644
--- a/webapp/sass/components/_inputs.scss
+++ b/webapp/sass/components/_inputs.scss
@@ -37,3 +37,10 @@ fieldset {
.admin-textarea {
resize: none;
}
+
+input {
+ &[type='radio'],
+ &[type='checkbox'] {
+ margin-top: 1px;
+ }
+}
diff --git a/webapp/sass/components/_scrollbar.scss b/webapp/sass/components/_scrollbar.scss
index a4df8230d..66b476166 100644
--- a/webapp/sass/components/_scrollbar.scss
+++ b/webapp/sass/components/_scrollbar.scss
@@ -15,13 +15,15 @@
}
body {
- scrollbar-3dlight-color: #7D7E94;
- scrollbar-arrow-color: #C1C1D1;
- scrollbar-darkshadow-color: #2D2C4D;
- scrollbar-face-color: rgba(0, 0, 0, .1);
- scrollbar-highlight-color: #7D7E94;
- scrollbar-shadow-color: #2D2C4D;
- scrollbar-track-color: rgba(0, 0, 0, .1);
+ &.app__body {
+ scrollbar-3dlight-color: #7D7E94;
+ scrollbar-arrow-color: #C1C1D1;
+ scrollbar-darkshadow-color: #2D2C4D;
+ scrollbar-face-color: rgba(0, 0, 0, .1);
+ scrollbar-highlight-color: #7D7E94;
+ scrollbar-shadow-color: #2D2C4D;
+ scrollbar-track-color: rgba(0, 0, 0, .1);
+ }
}
.scrollbar--horizontal,
diff --git a/webapp/sass/components/_tooltip.scss b/webapp/sass/components/_tooltip.scss
index 5e71e3a7b..0049fe1b8 100644
--- a/webapp/sass/components/_tooltip.scss
+++ b/webapp/sass/components/_tooltip.scss
@@ -8,3 +8,9 @@
word-break: break-word;
}
}
+
+#webrtcTooltip {
+ .tooltip-inner {
+ word-break: normal;
+ }
+}
diff --git a/webapp/sass/components/_webrtc.scss b/webapp/sass/components/_webrtc.scss
index b025ab00c..c9deada26 100644
--- a/webapp/sass/components/_webrtc.scss
+++ b/webapp/sass/components/_webrtc.scss
@@ -2,10 +2,14 @@
.webrtc__user-profile {
@include webrtc-button;
+ position: absolute;
+ right: 7px;
text-align: center;
+ top: 5px;
#webrtc-btn {
- display: inherit;
+ height: 23px;
+ width: 23px;
}
}
@@ -15,6 +19,11 @@
margin-right: 10px;
position: relative;
top: 13px;
+
+ svg {
+ position: relative;
+ width: 20px;
+ }
}
.webrtc__notification--rhs {
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index 708fa4fca..25d95df46 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -1,6 +1,27 @@
@charset 'UTF-8';
@media screen and (max-width: 768px) {
+ .suggestion-list__content {
+ max-height: 145px;
+ }
+
+ .modal {
+ .modal-dialog {
+ &.new-channel__modal {
+ position: fixed;
+ @include translate3d(0, 100%, 0);
+ }
+ }
+
+ &.in {
+ .modal-dialog {
+ &.new-channel__modal {
+ @include translate3d(0, 0, 0);
+ }
+ }
+ }
+ }
+
.webrtc__notification--rhs {
left: auto;
right: 0;
@@ -17,15 +38,21 @@
}
#header-popover {
+ @include single-transition(all, .35s, ease);
+ @include translate3d(0, 100%, 0);
@include box-shadow(none);
background: alpha-color($black, .8);
border: none;
- height: 100%;
+ height: calc(100% - 45px);
max-width: 100%;
position: fixed;
top: 47px;
width: 100%;
+ &.in {
+ @include translate3d(0, 0, 0);
+ }
+
.arrow {
display: none;
}
@@ -37,23 +64,23 @@
}
.close {
- font-family: 'Open Sans', sans-serif;
- display: block;
- position: fixed;
+ @include border-radius(50%);
+ border: 1px solid $white;
bottom: 25px;
+ color: $white;
+ display: block;
+ font-family: 'Open Sans', sans-serif;
+ font-size: 23px;
+ font-weight: 200;
height: 30px;
- width: 30px;
- margin-left: -15px;
left: 50%;
- font-size: 23px;
line-height: 27px;
- border-radius: 50%;
- border: 1px solid $white;
- text-align: center;
+ margin-left: -15px;
opacity: 1;
+ position: fixed;
+ text-align: center;
text-shadow: none;
- color: white;
- font-weight: 200;
+ width: 30px;
}
}
@@ -183,6 +210,11 @@
}
}
+ .post__remove {
+ margin-right: 10px;
+ visibility: visible;
+ }
+
&.post--compact {
.status-wrapper {
&:after {
@@ -315,6 +347,24 @@
}
&.same--user {
+ &.post--compact {
+ .status-wrapper {
+ &:after {
+ bottom: -2px;
+ }
+ }
+ }
+
+ .post__img {
+ .status-wrapper {
+ display: inline-block;
+ }
+
+ img {
+ display: block;
+ }
+ }
+
.post__header {
height: auto;
margin-top: 5px;
@@ -743,34 +793,47 @@
white-space: nowrap;
}
+ .dropdown {
+ &.open {
+ .dropdown-menu {
+ @include translate3d(0, 0, 0);
+ visibility: visible;
+ }
+ }
+ }
+
.dropdown-menu {
+ @include single-transition(all, .35s, ease);
+ @include translate3d(0, 100%, 0);
background: alpha-color($black, .9);
- height: 100%;
+ display: block;
+ height: calc(100% - 45px);
left: 0;
overflow: auto;
- padding: 1.4em 0 140px;
+ padding: 1.4em 0 0;
position: fixed;
top: 42px;
+ visibility: hidden;
width: 100%;
.close {
- font-family: 'Open Sans', sans-serif;
- display: block;
- position: fixed;
+ @include border-radius(50%);
+ border: 1px solid $white;
bottom: 25px;
+ color: $white;
+ display: block;
+ font-family: 'Open Sans', sans-serif;
+ font-size: 23px;
+ font-weight: 200;
height: 30px;
- width: 30px;
- margin-left: -15px;
left: 50%;
- font-size: 23px;
line-height: 27px;
- border-radius: 50%;
- border: 1px solid $white;
- text-align: center;
+ margin-left: -15px;
opacity: 1;
+ position: fixed;
+ text-align: center;
text-shadow: none;
- color: white;
- font-weight: 200;
+ width: 30px;
}
> li {
@@ -1056,7 +1119,7 @@
}
&.move--right {
- @include translate3d(290px, 0, 0);
+ @include translate3d(0, 0, 0);
&:before {
@include single-transition(all, .35s, ease);
@@ -1281,11 +1344,7 @@
}
.post__header {
- .col__name {
- .user-popover {
- max-width: 100%;
- }
- }
+ padding-right: 50px;
}
}
}
@@ -1337,7 +1396,6 @@
.modal-body {
padding-bottom: 35px;
}
-
}
.settings-modal {
@@ -1395,7 +1453,7 @@
.inner-wrap {
&.move--right {
- @include translate3d(260px, 0, 0);
+ @include translate3d(0, 0, 0);
}
}
@@ -1416,26 +1474,41 @@
}
@media screen and (max-width: 320px) {
+ .post {
+ .post__header {
+ .col__name {
+ .user-popover {
+ max-width: 105px;
+ }
+ }
+ }
+ }
+
.tutorial-steps__container {
.tutorial__content {
.tutorial__steps {
padding: 0 20px;
width: 100%;
+
h1 {
font-size: 35px;
margin: -5px 0 20px;
}
+
h3 {
margin-bottom: 10px;
}
+
.tutorial__app-icons {
margin: 10px 0;
}
+
.tutorial__circles {
- margin: 0 0 15px;
bottom: 60px;
+ margin: 0 0 15px;
position: absolute;
}
+
.tutorial__footer {
bottom: 30px;
position: absolute;
@@ -1451,13 +1524,30 @@
}
}
+@media screen and (max-width: 320px) and (max-height: 560px) {
+ #navbar {
+ .navbar-default {
+ .dropdown-menu {
+ padding-top: 1em;
+
+ > li {
+ > a {
+ border: none;
+ line-height: 28px;
+ }
+ }
+ }
+ }
+ }
+}
+
// on iOS, allow clicks within an input's label to actually propagate through to the input itself
// http://stackoverflow.com/a/34810294/6325807
label span {
pointer-events: none;
}
-@media screen and (-webkit-min-device-pixel-ratio:0) {
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
select,
textarea,
input {
diff --git a/webapp/sass/responsive/_tablet.scss b/webapp/sass/responsive/_tablet.scss
index 52d7a6a77..f9cd0869d 100644
--- a/webapp/sass/responsive/_tablet.scss
+++ b/webapp/sass/responsive/_tablet.scss
@@ -9,7 +9,7 @@
}
.heading {
- width: 130px;
+ max-width: 150px;
}
}
@@ -73,7 +73,7 @@
}
.sidebar--right {
- @include single-transition(all, .5s, ease);
+ @include single-transition(all, .35s, ease);
@include translateX(100%);
&.move--left,
diff --git a/webapp/sass/routes/_admin-console.scss b/webapp/sass/routes/_admin-console.scss
index aa90a7eaf..5107cff3d 100644
--- a/webapp/sass/routes/_admin-console.scss
+++ b/webapp/sass/routes/_admin-console.scss
@@ -56,12 +56,16 @@
&.btn-spacing--right {
margin-right: 10px;
}
+
+ .fa {
+ margin-right: 5px;
+ }
}
.log__panel {
background-color: white;
border: 1px solid #ddd;
- height: 800px;
+ height: calc(100vh - 200px);
margin-top: 10px;
overflow: scroll;
padding: 5px;
diff --git a/webapp/sass/routes/_settings.scss b/webapp/sass/routes/_settings.scss
index 6fa8c26a7..6b7f8a5f1 100644
--- a/webapp/sass/routes/_settings.scss
+++ b/webapp/sass/routes/_settings.scss
@@ -236,12 +236,12 @@
}
.theme-elements__header {
- border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid;
cursor: pointer;
font-size: em(13.5px);
font-weight: 600;
margin: 10px 20px 0 0;
- padding: 5px 0 10px;
+ padding: 1px 0 10px;
.fa-minus {
display: none;
diff --git a/webapp/sass/routes/_signup.scss b/webapp/sass/routes/_signup.scss
index d9aa171a9..30e80cccb 100644
--- a/webapp/sass/routes/_signup.scss
+++ b/webapp/sass/routes/_signup.scss
@@ -13,7 +13,7 @@
.signup-team__container {
margin: 0 auto;
- max-width: 380px;
+ max-width: 400px;
padding: 100px 0 50px;
position: relative;
diff --git a/webapp/sass/utils/_mixins.scss b/webapp/sass/utils/_mixins.scss
index e252086ae..6a041d6ec 100644
--- a/webapp/sass/utils/_mixins.scss
+++ b/webapp/sass/utils/_mixins.scss
@@ -32,68 +32,27 @@
@mixin webrtc-button {
.webrtc__button {
@include border-radius(50px);
+ background: $button--ready;
display: block;
- height: 32px;
- width: 32px;
+ height: 33px;
+ text-align: center;
+ width: 33px;
&.on,
&:hover {
background: darken($button--ready, 5%);
}
- &:hover circle {
- fill: darken($button--ready, 5%);
- }
-
- circle {
- fill: $button--ready;
-
- &.offline {
- fill: $video-circle-offline;
- }
- }
-
- path {
- .on {
- display: none;
- }
-
- .off {
- display: block;
- }
- }
-
- &.on {
- path {
- .on {
- display: block;
- }
-
- .off {
- display: none;
- }
- }
+ &.offline {
+ background: $video-circle-offline;
- circle {
- fill-opacity: 0;
+ &:hover {
+ background: $video-circle-offline;
}
}
- }
- a {
- &[disabled] {
- .webrtc__button {
- &:hover {
- background: none;
- box-shadow: none;
- }
-
- &:hover {
- circle {
- fill: $video-circle-offline;
- }
- }
- }
+ svg {
+ fill: $white;
}
}
}
diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx
index 99aebc466..fcd177662 100644
--- a/webapp/stores/browser_store.jsx
+++ b/webapp/stores/browser_store.jsx
@@ -203,7 +203,11 @@ class BrowserStoreClass {
}
hasSeenLandingPage() {
- return JSON.parse(sessionStorage.getItem('__landingPageSeen__'));
+ if (this.isLocalStorageSupported()) {
+ return JSON.parse(sessionStorage.getItem('__landingPageSeen__'));
+ }
+
+ return true;
}
setLandingPageSeen(landingPageSeen) {
diff --git a/webapp/stores/integration_store.jsx b/webapp/stores/integration_store.jsx
index 33680452b..ae818b443 100644
--- a/webapp/stores/integration_store.jsx
+++ b/webapp/stores/integration_store.jsx
@@ -137,6 +137,15 @@ class IntegrationStore extends EventEmitter {
this.setCommands(teamId, commands);
}
+ editCommand(command) {
+ const teamId = command.team_id;
+ const commands = this.getCommands(teamId);
+
+ commands.push(command);
+
+ this.setCommands(teamId, commands);
+ }
+
updateCommand(command) {
const teamId = command.team_id;
const commands = this.getCommands(teamId);
diff --git a/webapp/stores/notification_store.jsx b/webapp/stores/notification_store.jsx
index dc707b50e..d5e8acb4d 100644
--- a/webapp/stores/notification_store.jsx
+++ b/webapp/stores/notification_store.jsx
@@ -26,6 +26,9 @@ class NotificationStoreClass extends EventEmitter {
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
+ setFocus(focus) {
+ this.inFocus = focus;
+ }
handleRecievedPost(post, msgProps) {
// Send desktop notification
@@ -98,11 +101,21 @@ class NotificationStoreClass extends EventEmitter {
duration = parseInt(user.notify_props.desktop_duration, 10) * 1000;
}
+ //Play a sound if explicitly set in settings
const sound = !user.notify_props || user.notify_props.desktop_sound === 'true';
- Utils.notifyMe(title, body, channel, teamId, duration, !sound);
- if (sound && !UserAgent.isWindowsApp() && !UserAgent.isMacApp()) {
- Utils.ding();
+ // Notify if you're not looking in the right channel or when
+ // the window itself is not active
+ const activeChannel = ChannelStore.getCurrent();
+ const notify = activeChannel.id !== channel.id || !this.inFocus;
+
+ if (notify) {
+ Utils.notifyMe(title, body, channel, teamId, duration, !sound);
+
+ //Don't add extra sounds on native desktop clients
+ if (sound && !UserAgent.isWindowsApp() && !UserAgent.isMacApp() && !UserAgent.isMobileApp()) {
+ Utils.ding();
+ }
}
}
}
@@ -118,6 +131,9 @@ NotificationStore.dispatchToken = AppDispatcher.register((payload) => {
NotificationStore.handleRecievedPost(action.post, action.websocketMessageProps);
NotificationStore.emitChange();
break;
+ case ActionTypes.BROWSER_CHANGE_FOCUS:
+ NotificationStore.setFocus(action.focus);
+ break;
}
});
diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx
index a4e49fc98..fbe5cd457 100644
--- a/webapp/stores/post_store.jsx
+++ b/webapp/stores/post_store.jsx
@@ -245,7 +245,7 @@ class PostStoreClass extends EventEmitter {
this.postsInfo[id].postList = combinedPosts;
}
- storePost(post) {
+ storePost(post, isNewPost = false) {
const postList = makePostListNonNull(this.getAllPosts(post.channel_id));
if (post.pending_post_id !== '') {
@@ -255,7 +255,7 @@ class PostStoreClass extends EventEmitter {
post.pending_post_id = '';
postList.posts[post.id] = post;
- if (postList.order.indexOf(post.id) === -1) {
+ if (isNewPost && postList.order.indexOf(post.id) === -1) {
postList.order.unshift(post.id);
}
@@ -629,7 +629,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
PostStore.emitChange();
break;
case ActionTypes.RECEIVED_POST:
- PostStore.storePost(action.post);
+ PostStore.storePost(action.post, true);
PostStore.emitChange();
break;
case ActionTypes.RECEIVED_EDIT_POST:
diff --git a/webapp/stores/search_store.jsx b/webapp/stores/search_store.jsx
index d1458ceed..46a086ddb 100644
--- a/webapp/stores/search_store.jsx
+++ b/webapp/stores/search_store.jsx
@@ -96,6 +96,21 @@ class SearchStoreClass extends EventEmitter {
this.isMentionSearch = isMentionSearch;
this.isFlaggedPosts = isFlaggedPosts;
}
+
+ deletePost(post) {
+ const results = this.getSearchResults();
+ if (results == null) {
+ return;
+ }
+
+ if (post.id in results.posts) {
+ // make sure to copy the post so that component state changes work properly
+ results.posts[post.id] = Object.assign({}, post, {
+ state: Constants.POST_DELETED,
+ file_ids: []
+ });
+ }
+ }
}
var SearchStore = new SearchStoreClass();
@@ -115,6 +130,10 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.SHOW_SEARCH:
SearchStore.emitShowSearch();
break;
+ case ActionTypes.POST_DELETED:
+ SearchStore.deletePost(action.post);
+ SearchStore.emitSearchChange();
+ break;
default:
}
});
diff --git a/webapp/stores/team_store.jsx b/webapp/stores/team_store.jsx
index 858e2688e..7775a4a3a 100644
--- a/webapp/stores/team_store.jsx
+++ b/webapp/stores/team_store.jsx
@@ -265,6 +265,10 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
TeamStore.appendMyTeamMember(action.member);
TeamStore.emitChange();
break;
+ case ActionTypes.UPDATE_TEAM:
+ TeamStore.saveTeam(action.team);
+ TeamStore.emitChange();
+ break;
case ActionTypes.RECEIVED_ALL_TEAMS:
TeamStore.saveTeams(action.teams);
TeamStore.emitChange();
diff --git a/webapp/tests/client_command.test.jsx b/webapp/tests/client_command.test.jsx
index 769fa2fa0..7d39537f8 100644
--- a/webapp/tests/client_command.test.jsx
+++ b/webapp/tests/client_command.test.jsx
@@ -81,6 +81,34 @@ describe('Client.Commands', function() {
});
});
+ it('editCommand', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
+
+ var cmd = {};
+ cmd.url = 'http://www.gonowhere.com';
+ cmd.trigger = '/hello';
+ cmd.method = 'P';
+ cmd.username = '';
+ cmd.icon_url = '';
+ cmd.auto_complete = false;
+ cmd.auto_complete_desc = '';
+ cmd.auto_complete_hint = '';
+ cmd.display_name = 'Unit Test';
+
+ TestHelper.basicClient().editCommand(
+ cmd,
+ function() {
+ done(new Error('cmds not enabled'));
+ },
+ function(err) {
+ assert.equal(err.id, 'api.command.disabled.app_error');
+ done();
+ }
+ );
+ });
+ });
+
it('deleteCommand', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
diff --git a/webapp/tests/client_user.test.jsx b/webapp/tests/client_user.test.jsx
index 5e70c5c3e..2e5b80dd7 100644
--- a/webapp/tests/client_user.test.jsx
+++ b/webapp/tests/client_user.test.jsx
@@ -36,6 +36,21 @@ describe('Client.User', function() {
});
});
+ it('getByUsername', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getByUsername(
+ TestHelper.basicUser().username,
+ function(data) {
+ assert.equal(data.username, TestHelper.basicUser().username);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
it('getInitialLoad', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().getInitialLoad(
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index c281f2893..71fbc8db0 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -1360,6 +1360,29 @@ export function addCommand(command, success, error) {
);
}
+export function editCommand(command, success, error) {
+ Client.editCommand(
+ command,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_COMMAND,
+ command: data
+ });
+
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ } else {
+ dispatchError(err, 'editCommand');
+ }
+ }
+ );
+}
+
export function deleteCommand(id) {
Client.deleteCommand(
id,
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index 899e4e5a4..36d8cdb2a 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -147,10 +147,10 @@ export function createDefaultIntroMessage(channel, centeredIntro) {
export function createStandardIntroMessage(channel, centeredIntro) {
var uiName = channel.display_name;
- var creatorName = '';
-
+ var creatorName = Utils.displayUsername(channel.creator_id);
var uiType;
var memberMessage;
+
if (channel.type === 'P') {
uiType = (
<FormattedMessage
@@ -204,14 +204,30 @@ export function createStandardIntroMessage(channel, centeredIntro) {
} else {
createMessage = (
<span>
- <FormattedHTMLMessage
+ <FormattedMessage
id='intro_messages.creator'
- defaultMessage='This is the start of the <strong>{name}</strong> {type}, created by <strong>{creator}</strong> on <strong>{date}</strong>'
+ defaultMessage='This is the start of the {name} {type}, created by {creator} on {date}.'
values={{
name: (uiName),
type: (uiType),
- date,
- creator: creatorName
+ creator: (creatorName),
+ date
+ }}
+ />
+ </span>
+ );
+ }
+
+ var purposeMessage = '';
+ if (channel.purpose && channel.purpose !== '') {
+ purposeMessage = (
+ <span>
+ <FormattedMessage
+ id='intro_messages.purpose'
+ defaultMessage=" This {type}'s purpose is: {purpose}"
+ values={{
+ purpose: channel.purpose,
+ type: (uiType)
}}
/>
</span>
@@ -232,6 +248,7 @@ export function createStandardIntroMessage(channel, centeredIntro) {
<p className='channel-intro__content'>
{createMessage}
{memberMessage}
+ {purposeMessage}
<br/>
</p>
{createInviteChannelMemberButton(channel, uiType)}
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 95d1f01f5..21ec07db3 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -16,16 +16,16 @@ import genericIcon from 'images/icons/generic.png';
import logoImage from 'images/logo_compact.png';
import logoWebhook from 'images/webhook_icon.jpg';
-import solarizedDarkCSS from '!!file?name=files/code_themes/[hash].[ext]!highlight.js/styles/solarized-dark.css';
+import solarizedDarkCSS from '!!file-loader?name=files/code_themes/[hash].[ext]!highlight.js/styles/solarized-dark.css';
import solarizedDarkIcon from 'images/themes/code_themes/solarized-dark.png';
-import solarizedLightCSS from '!!file?name=files/code_themes/[hash].[ext]!highlight.js/styles/solarized-light.css';
+import solarizedLightCSS from '!!file-loader?name=files/code_themes/[hash].[ext]!highlight.js/styles/solarized-light.css';
import solarizedLightIcon from 'images/themes/code_themes/solarized-light.png';
-import githubCSS from '!!file?name=files/code_themes/[hash].[ext]!highlight.js/styles/github.css';
+import githubCSS from '!!file-loader?name=files/code_themes/[hash].[ext]!highlight.js/styles/github.css';
import githubIcon from 'images/themes/code_themes/github.png';
-import monokaiCSS from '!!file?name=files/code_themes/[hash].[ext]!highlight.js/styles/monokai.css';
+import monokaiCSS from '!!file-loader?name=files/code_themes/[hash].[ext]!highlight.js/styles/monokai.css';
import monokaiIcon from 'images/themes/code_themes/monokai.png';
import defaultThemeImage from 'images/themes/organization.png';
@@ -126,6 +126,7 @@ export const ActionTypes = keyMirror({
RECEIVED_MY_TEAM: null,
CREATED_TEAM: null,
+ UPDATE_TEAM: null,
RECEIVED_CONFIG: null,
RECEIVED_LOGS: null,
@@ -156,7 +157,9 @@ export const ActionTypes = keyMirror({
SUGGESTION_CLEAR_SUGGESTIONS: null,
SUGGESTION_COMPLETE_WORD: null,
SUGGESTION_SELECT_NEXT: null,
- SUGGESTION_SELECT_PREVIOUS: null
+ SUGGESTION_SELECT_PREVIOUS: null,
+
+ BROWSER_CHANGE_FOCUS: null
});
export const WebrtcActionTypes = keyMirror({
@@ -374,6 +377,7 @@ export const Constants = {
COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>",
REPLY_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-158 242 18 18' style='enable-background:new -158 242 18 18;' xml:space='preserve'> <path d='M-142.2,252.6c-2-3-4.8-4.7-8.3-4.8v-3.3c0-0.2-0.1-0.3-0.2-0.3s-0.3,0-0.4,0.1l-6.9,6.2c-0.1,0.1-0.1,0.2-0.1,0.3 c0,0.1,0,0.2,0.1,0.3l6.9,6.4c0.1,0.1,0.3,0.1,0.4,0.1c0.1-0.1,0.2-0.2,0.2-0.4v-3.8c4.2,0,7.4,0.4,9.6,4.4c0.1,0.1,0.2,0.2,0.3,0.2 c0,0,0.1,0,0.1,0c0.2-0.1,0.3-0.3,0.2-0.4C-140.2,257.3-140.6,255-142.2,252.6z M-150.8,252.5c-0.2,0-0.4,0.2-0.4,0.4v3.3l-6-5.5 l6-5.3v2.8c0,0.2,0.2,0.4,0.4,0.4c3.3,0,6,1.5,8,4.5c0.5,0.8,0.9,1.6,1.2,2.3C-144,252.8-147.1,252.5-150.8,252.5z'/> </svg>",
SCROLL_BOTTOM_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-239 239 21 23' style='enable-background:new -239 239 21 23;' xml:space='preserve'> <path d='M-239,241.4l2.4-2.4l8.1,8.2l8.1-8.2l2.4,2.4l-10.5,10.6L-239,241.4z M-228.5,257.2l8.1-8.2l2.4,2.4l-10.5,10.6l-10.5-10.6 l2.4-2.4L-228.5,257.2z'/> </svg>",
+ VIDEO_ICON: "<svg width='55%'height='100%'viewBox='0 0 13 8'> <g transform='matrix(1,0,0,1,-507,-146)'> <g transform='matrix(0.0133892,0,0,0.014499,500.635,142.838)'> <path d='M1158,547.286L1158,644.276C1158,684.245 1125.55,716.694 1085.58,716.694L579.341,716.694C539.372,716.694 506.922,684.245 506.922,644.276L506.922,306.322C506.922,266.353 539.371,233.904 579.341,233.903L1085.58,233.903C1125.55,233.904 1158,266.353 1158,306.322L1158,402.939L1359.75,253.14C1365.83,248.362 1373.43,245.973 1382.56,245.973C1386.61,245.973 1390.83,246.602 1395.22,247.859C1408.4,252.134 1414.99,259.552 1414.99,270.113L1414.99,680.485C1414.99,691.046 1408.4,698.464 1395.22,702.739C1390.83,703.996 1386.61,704.624 1382.56,704.624C1373.43,704.624 1365.83,702.236 1359.75,697.458L1158,547.286Z'/> </g> </g> </svg>",
UPDATE_TYPING_MS: 5000,
THEMES: {
default: {
@@ -817,6 +821,7 @@ export const Constants = {
DEFAULT_MAX_USERS_PER_TEAM: 50,
MIN_TEAMNAME_LENGTH: 2,
DEFAULT_MAX_CHANNELS_PER_TEAM: 2000,
+ DEFAULT_MAX_NOTIFICATIONS_PER_CHANNEL: 1000,
MAX_TEAMNAME_LENGTH: 15,
MIN_USERNAME_LENGTH: 3,
MAX_USERNAME_LENGTH: 22,
diff --git a/webapp/utils/emoticons.jsx b/webapp/utils/emoticons.jsx
index ba54d8cb3..26443b120 100644
--- a/webapp/utils/emoticons.jsx
+++ b/webapp/utils/emoticons.jsx
@@ -12,7 +12,7 @@ export const emoticonPatterns = {
smile: /(^|\s)(:-?d)(?=$|\s)/gi, // :D
stuck_out_tongue_closed_eyes: /(^|\s)(x-d)(?=$|\s)/gi, // x-d
stuck_out_tongue: /(^|\s)(:-?p)(?=$|\s)/gi, // :p
- rage: /(^|\s)(:-?[\[@])(?=$|\s)/g, // :@
+ rage: /(^|\s)(:-?[[@])(?=$|\s)/g, // :@
slightly_frowning_face: /(^|\s)(:-?\()(?=$|\s)/g, // :(
cry: /(^|\s)(:['’]-?\(|:&#x27;\(|:&#39;\()(?=$|\s)/g, // :`(
confused: /(^|\s)(:-?\/)(?=$|\s)/g, // :/
@@ -23,7 +23,7 @@ export const emoticonPatterns = {
heart: /(^|\s)(<3|&lt;3)(?=$|\s)/g, // <3
broken_heart: /(^|\s)(<\/3|&lt;&#x2F;3)(?=$|\s)/g, // </3
thumbsup: /(^|\s)(:\+1:)(?=$|\s)/g, // :+1:
- thumbsdown: /(^|\s)(:\-1:)(?=$|\s)/g // :-1:
+ thumbsdown: /(^|\s)(:-1:)(?=$|\s)/g // :-1:
};
export function handleEmoticons(text, tokens, emojis) {
@@ -38,7 +38,7 @@ export function handleEmoticons(text, tokens, emojis) {
// we have an image path so we found a matching emoticon
tokens.set(alias, {
- value: `<img align="absmiddle" alt="${matchText}" class="emoticon" src="${path}" title="${matchText}" />`,
+ value: `<span alt="${matchText}" class="emoticon" title="${matchText}" style="background-image:url(${path})"></span>`,
originalText: fullMatch
});
diff --git a/webapp/utils/markdown.jsx b/webapp/utils/markdown.jsx
index 0b279ca6d..8a0b9ef0a 100644
--- a/webapp/utils/markdown.jsx
+++ b/webapp/utils/markdown.jsx
@@ -197,7 +197,7 @@ class MattermostMarkdownRenderer extends marked.Renderer {
}
listitem(text, bullet) {
- const taskListReg = /^\[([ |xX])\] /;
+ const taskListReg = /^\[([ |xX])] /;
const isTaskList = taskListReg.exec(text);
if (isTaskList) {
diff --git a/webapp/utils/text_formatting.jsx b/webapp/utils/text_formatting.jsx
index 5caad05c3..9c2edf954 100644
--- a/webapp/utils/text_formatting.jsx
+++ b/webapp/utils/text_formatting.jsx
@@ -249,7 +249,7 @@ function autolinkChannelMentions(text, tokens, channelNamesMap, team) {
}
export function escapeRegex(text) {
- return text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ return text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
}
function highlightCurrentMentions(text, tokens, mentionKeys = []) {
@@ -386,7 +386,7 @@ function parseSearchTerms(searchTerm) {
termString = termString.substring(captured[0].length);
// break the text up into words based on how the server splits them in SqlPostStore.SearchPosts and then discard empty terms
- terms.push(...captured[0].split(/[ <>+\(\)~@]/).filter((term) => Boolean(term)));
+ terms.push(...captured[0].split(/[ <>+()~@]/).filter((term) => Boolean(term)));
continue;
}
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index d2ea25f22..5c2823f41 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -30,8 +30,8 @@ export function isEmail(email) {
export function cleanUpUrlable(input) {
var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-');
cleaned = cleaned.replace(/-{2,}/, '-');
- cleaned = cleaned.replace(/^\-+/, '');
- cleaned = cleaned.replace(/\-+$/, '');
+ cleaned = cleaned.replace(/^-+/, '');
+ cleaned = cleaned.replace(/-+$/, '');
return cleaned;
}
@@ -135,7 +135,6 @@ export function ding() {
canDing = false;
setTimeout(() => {
canDing = true;
- return;
}, 3000);
}
}
@@ -194,14 +193,14 @@ export function getTimestamp() {
// extracts links not styled by Markdown
export function extractFirstLink(text) {
- const pattern = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/i;
+ const pattern = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[-A-Z0-9+\u0026\u2019@#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026@#/%=~()_|])/i;
let inText = text;
// strip out code blocks
inText = inText.replace(/`[^`]*`/g, '');
// strip out inline markdown images
- inText = inText.replace(/!\[[^\]]*\]\([^\)]*\)/g, '');
+ inText = inText.replace(/!\[[^\]]*]\([^)]*\)/g, '');
const match = pattern.exec(inText);
if (match) {
@@ -212,7 +211,7 @@ export function extractFirstLink(text) {
}
export function escapeRegExp(string) {
- return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
+ return string.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
}
// Taken from http://stackoverflow.com/questions/1068834/object-comparison-in-javascript and modified slightly
@@ -463,201 +462,202 @@ export function isHexColor(value) {
export function applyTheme(theme) {
if (theme.sidebarBg) {
- changeCss('.sidebar--left, .sidebar--left .sidebar__divider .sidebar__divider__text, .app__body .modal .settings-modal .settings-table .settings-links, .app__body .sidebar--menu', 'background:' + theme.sidebarBg, 1);
- changeCss('body.app__body', 'scrollbar-face-color:' + theme.sidebarBg, 3);
- changeCss('@media(max-width: 768px){.app__body .modal .settings-modal:not(.settings-modal--tabless):not(.display--content) .modal-content', 'background:' + theme.sidebarBg, 1);
+ changeCss('.sidebar--left, .sidebar--left .sidebar__divider .sidebar__divider__text, .app__body .modal .settings-modal .settings-table .settings-links, .app__body .sidebar--menu', 'background:' + theme.sidebarBg);
+ changeCss('body.app__body', 'scrollbar-face-color:' + theme.sidebarBg);
+ changeCss('@media(max-width: 768px){.app__body .modal .settings-modal:not(.settings-modal--tabless):not(.display--content) .modal-content', 'background:' + theme.sidebarBg);
}
if (theme.sidebarText) {
- changeCss('.app__body .ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y', 'background:' + theme.sidebarText, 1);
- changeCss('.app__body .ps-container:hover .ps-scrollbar-y-rail:hover', 'background:' + changeOpacity(theme.sidebarText, 0.15), 1);
- changeCss('.sidebar--left .nav-pills__container li>a, .app__body .sidebar--right, .app__body .modal .settings-modal .nav-pills>li a, .app__body .sidebar--menu', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1);
- changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1);
- changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1);
- changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1);
- changeCss('.sidebar--left .status .offline--icon', 'fill:' + theme.sidebarText, 1);
- changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a, .app__body .sidebar--menu .divider', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2);
+ changeCss('.app__body .ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y', 'background:' + theme.sidebarText);
+ changeCss('.app__body .ps-container:hover .ps-scrollbar-y-rail:hover', 'background:' + changeOpacity(theme.sidebarText, 0.15));
+ changeCss('.sidebar--left .nav-pills__container li>a, .app__body .sidebar--right, .app__body .modal .settings-modal .nav-pills>li a, .app__body .sidebar--menu', 'color:' + changeOpacity(theme.sidebarText, 0.6));
+ changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText);
+ changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6));
+ changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText);
+ changeCss('.sidebar--left .status .offline--icon', 'fill:' + theme.sidebarText);
+ changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li>a, .app__body .sidebar--menu .divider', 'border-color:' + changeOpacity(theme.sidebarText, 0.2));
}
if (theme.sidebarUnreadText) {
- changeCss('.sidebar--left .nav-pills__container li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;', 2);
+ changeCss('.sidebar--left .nav-pills__container li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;');
}
if (theme.sidebarTextHoverBg) {
- changeCss('.sidebar--left .nav-pills__container li>a:hover, .app__body .modal .settings-modal .nav-pills>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1);
- changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1);
+ changeCss('.sidebar--left .nav-pills__container li>a:hover, .app__body .modal .settings-modal .nav-pills>li:hover a', 'background:' + theme.sidebarTextHoverBg);
+ changeCss('@media(max-width: 768px){.app__body .modal .settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg);
}
if (theme.sidebarTextActiveBorder) {
- changeCss('.sidebar--left .nav li.active a:before, .app__body .modal .settings-modal .nav-pills>li.active a:before', 'background:' + theme.sidebarTextActiveBorder, 1);
- changeCss('.sidebar--left .sidebar__divider:before', 'background:' + changeOpacity(theme.sidebarTextActiveBorder, 0.5), 1);
- changeCss('.sidebar--left .sidebar__divider', 'color:' + theme.sidebarTextActiveBorder, 1);
+ changeCss('.sidebar--left .nav li.active a:before, .app__body .modal .settings-modal .nav-pills>li.active a:before', 'background:' + theme.sidebarTextActiveBorder);
+ changeCss('.sidebar--left .sidebar__divider:before', 'background:' + changeOpacity(theme.sidebarTextActiveBorder, 0.5));
+ changeCss('.sidebar--left .sidebar__divider', 'color:' + theme.sidebarTextActiveBorder);
}
if (theme.sidebarTextActiveColor) {
- changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .app__body .modal .settings-modal .nav-pills>li.active a, .app__body .modal .settings-modal .nav-pills>li.active a:hover, .app__body .modal .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2);
- changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + changeOpacity(theme.sidebarTextActiveColor, 0.1), 1);
+ changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .app__body .modal .settings-modal .nav-pills>li.active a, .app__body .modal .settings-modal .nav-pills>li.active a:hover, .app__body .modal .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor);
+ changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + changeOpacity(theme.sidebarTextActiveColor, 0.1));
}
if (theme.sidebarHeaderBg) {
- changeCss('.sidebar--left .team__header, .app__body .sidebar--menu .team__header, .app__body .post-list__timestamp > div', 'background:' + theme.sidebarHeaderBg, 1);
- changeCss('.app__body .modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
- changeCss('.app__body #navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
- changeCss('@media(max-width: 768px){.app__body .search-bar__container', 'background:' + theme.sidebarHeaderBg, 1);
- changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg, 1);
+ changeCss('.sidebar--left .team__header, .app__body .sidebar--menu .team__header, .app__body .post-list__timestamp > div', 'background:' + theme.sidebarHeaderBg);
+ changeCss('.app__body .modal .modal-header', 'background:' + theme.sidebarHeaderBg);
+ changeCss('.app__body #navbar .navbar-default', 'background:' + theme.sidebarHeaderBg);
+ changeCss('@media(max-width: 768px){.app__body .search-bar__container', 'background:' + theme.sidebarHeaderBg);
+ changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg);
}
if (theme.sidebarHeaderTextColor) {
- changeCss('.sidebar--left .team__header .header__info, .app__body .sidebar--menu .team__header .header__info, .app__body .post-list__timestamp > div', 'color:' + theme.sidebarHeaderTextColor, 1);
- changeCss('.app__body .sidebar-header-dropdown__icon', 'fill:' + theme.sidebarHeaderTextColor, 1);
- changeCss('.sidebar--left .team__header .user__name, .app__body .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1);
- changeCss('.sidebar--left .team__header:hover .user__name, .app__body .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1);
- changeCss('.app__body .modal .modal-header .modal-title, .app__body .modal .modal-header .modal-title .name, .app__body .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor, 1);
- changeCss('.app__body #navbar .navbar-default .navbar-brand .heading', 'color:' + theme.sidebarHeaderTextColor, 1);
- changeCss('.app__body #navbar .navbar-default .navbar-toggle .icon-bar, ', 'background:' + theme.sidebarHeaderTextColor, 1);
- changeCss('@media(max-width: 768px){.app__body .search-bar__container', 'color:' + theme.sidebarHeaderTextColor, 2);
+ changeCss('.sidebar--left .team__header .header__info, .app__body .sidebar--menu .team__header .header__info, .app__body .post-list__timestamp > div', 'color:' + theme.sidebarHeaderTextColor);
+ changeCss('.app__body .sidebar-header-dropdown__icon', 'fill:' + theme.sidebarHeaderTextColor);
+ changeCss('.sidebar--left .team__header .user__name, .app__body .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8));
+ changeCss('.sidebar--left .team__header:hover .user__name, .app__body .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor);
+ changeCss('.app__body .modal .modal-header .modal-title, .app__body .modal .modal-header .modal-title .name, .app__body .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor);
+ changeCss('.app__body #navbar .navbar-default .navbar-brand .heading', 'color:' + theme.sidebarHeaderTextColor);
+ changeCss('.app__body #navbar .navbar-default .navbar-toggle .icon-bar', 'background:' + theme.sidebarHeaderTextColor);
+ changeCss('@media(max-width: 768px){.app__body .search-bar__container', 'color:' + theme.sidebarHeaderTextColor);
}
if (theme.onlineIndicator) {
- changeCss('.app__body .status .online--icon', 'fill:' + theme.onlineIndicator, 1);
- changeCss('.app__body .channel-header__info .status .online--icon', 'fill:' + theme.onlineIndicator, 1);
- changeCss('.app__body .navbar .status .online--icon', 'fill:' + theme.onlineIndicator, 1);
- changeCss('.status-wrapper.status-online:after', 'background:' + theme.onlineIndicator, 1);
+ changeCss('.app__body .status .online--icon', 'fill:' + theme.onlineIndicator);
+ changeCss('.app__body .channel-header__info .status .online--icon', 'fill:' + theme.onlineIndicator);
+ changeCss('.app__body .navbar .status .online--icon', 'fill:' + theme.onlineIndicator);
+ changeCss('.status-wrapper.status-online:after', 'background:' + theme.onlineIndicator);
}
if (theme.awayIndicator) {
- changeCss('.app__body .status .away--icon', 'fill:' + theme.awayIndicator, 1);
- changeCss('.app__body .channel-header__info .status .away--icon', 'fill:' + theme.awayIndicator, 1);
- changeCss('.app__body .navbar .status .away--icon', 'fill:' + theme.awayIndicator, 1);
- changeCss('.status-wrapper.status-away:after', 'background:' + theme.awayIndicator, 1);
+ changeCss('.app__body .status .away--icon', 'fill:' + theme.awayIndicator);
+ changeCss('.app__body .channel-header__info .status .away--icon', 'fill:' + theme.awayIndicator);
+ changeCss('.app__body .navbar .status .away--icon', 'fill:' + theme.awayIndicator);
+ changeCss('.status-wrapper.status-away:after', 'background:' + theme.awayIndicator);
}
if (theme.mentionBj) {
- changeCss('.sidebar--left .nav-pills__unread-indicator', 'background:' + theme.mentionBj, 1);
- changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj + '!important;', 1);
+ changeCss('.sidebar--left .nav-pills__unread-indicator', 'background:' + theme.mentionBj);
+ changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj + '!important;');
}
if (theme.mentionColor) {
- changeCss('.sidebar--left .nav-pills__unread-indicator', 'color:' + theme.mentionColor, 2);
- changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor + '!important;', 2);
+ changeCss('.sidebar--left .nav-pills__unread-indicator', 'color:' + theme.mentionColor);
+ changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor + '!important;');
}
if (theme.centerChannelBg) {
- changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'background:' + theme.centerChannelBg, 1);
- changeCss('.app__body .app__content, .app__body .markdown__table, .app__body .markdown__table tbody tr, .app__body .suggestion-list__content, .app__body .modal .modal-content, .app__body .modal .modal-footer, .app__body .post.post--compact .post-image__column, .app__body .suggestion-list__divider > span', 'background:' + theme.centerChannelBg, 1);
- changeCss('#post-list .post-list-holder-by-time, .app__body .post .dropdown-menu a', 'background:' + theme.centerChannelBg, 1);
- changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
- changeCss('.app__body .date-separator .separator__text, .app__body .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
- changeCss('.app__body .post-image__details, .app__body .search-help-popover .search-autocomplete__divider span', 'background:' + theme.centerChannelBg, 1);
- changeCss('.app__body .sidebar--right, .app__body .dropdown-menu, .app__body .popover, .app__body .tip-overlay', 'background:' + theme.centerChannelBg, 1);
- changeCss('.app__body .popover.bottom>.arrow:after', 'border-bottom-color:' + theme.centerChannelBg, 1);
- changeCss('.app__body .popover.right>.arrow:after, .app__body .tip-overlay.tip-overlay--sidebar .arrow, .app__body .tip-overlay.tip-overlay--header .arrow', 'border-right-color:' + theme.centerChannelBg, 1);
- changeCss('.app__body .popover.left>.arrow:after', 'border-left-color:' + theme.centerChannelBg, 1);
- changeCss('.app__body .popover.top>.arrow:after, .app__body .tip-overlay.tip-overlay--chat .arrow', 'border-top-color:' + theme.centerChannelBg, 1);
- changeCss('@media(min-width: 768px){.app__body .search-bar__container .search__form .search-bar, .app__body .form-control', 'background:' + theme.centerChannelBg, 1);
- changeCss('@media(min-width: 768px){.app__body .sidebar--right.sidebar--right--expanded .sidebar-right-container', 'background:' + theme.centerChannelBg, 1);
- changeCss('.app__body .attachment__content', 'background:' + theme.centerChannelBg, 1);
- changeCss('body.app__body', 'scrollbar-face-color:' + theme.centerChannelBg, 2);
- changeCss('body.app__body', 'scrollbar-track-color:' + theme.centerChannelBg, 2);
- changeCss('.app__body .post-list__new-messages-below', 'color:' + theme.centerChannelBg, 1);
+ changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .app__content, .app__body .markdown__table, .app__body .markdown__table tbody tr, .app__body .suggestion-list__content, .app__body .modal .modal-content, .app__body .modal .modal-footer, .app__body .post.post--compact .post-image__column, .app__body .suggestion-list__divider > span', 'background:' + theme.centerChannelBg);
+ changeCss('#post-list .post-list-holder-by-time, .app__body .post .dropdown-menu a', 'background:' + theme.centerChannelBg);
+ changeCss('#post-create', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .date-separator .separator__text, .app__body .new-separator .separator__text', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .post-image__details, .app__body .search-help-popover .search-autocomplete__divider span', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .sidebar--right, .app__body .dropdown-menu, .app__body .popover, .app__body .tip-overlay', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .popover.bottom>.arrow:after', 'border-bottom-color:' + theme.centerChannelBg);
+ changeCss('.app__body .popover.right>.arrow:after, .app__body .tip-overlay.tip-overlay--sidebar .arrow, .app__body .tip-overlay.tip-overlay--header .arrow', 'border-right-color:' + theme.centerChannelBg);
+ changeCss('.app__body .popover.left>.arrow:after', 'border-left-color:' + theme.centerChannelBg);
+ changeCss('.app__body .popover.top>.arrow:after, .app__body .tip-overlay.tip-overlay--chat .arrow', 'border-top-color:' + theme.centerChannelBg);
+ changeCss('@media(min-width: 768px){.app__body .search-bar__container .search__form .search-bar, .app__body .form-control', 'background:' + theme.centerChannelBg);
+ changeCss('@media(min-width: 768px){.app__body .sidebar--right.sidebar--right--expanded .sidebar-right-container', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .attachment__content', 'background:' + theme.centerChannelBg);
+ changeCss('body.app__body', 'scrollbar-face-color:' + theme.centerChannelBg);
+ changeCss('body.app__body', 'scrollbar-track-color:' + theme.centerChannelBg);
+ changeCss('.app__body .post-list__new-messages-below', 'color:' + theme.centerChannelBg);
}
if (theme.centerChannelColor) {
- changeCss('.app__body .post-list__arrows, .app__body .post .flag-icon__container', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
- changeCss('.app__body .modal .status .offline--icon, .app__body .channel-header__links .icon, .app__body .sidebar--right .sidebar--right__subheader .usage__icon', 'fill:' + theme.centerChannelColor, 1);
- changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
- changeCss('.app__body .post .dropdown-menu a, .sidebar--left, .app__body .sidebar--right .sidebar--right__header, .app__body .suggestion-list__content .command', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .post.post--system .post__body', 'color:' + changeOpacity(theme.centerChannelColor, 0.6), 1);
- changeCss('.app__body .input-group-addon, .app__body .app__content, .app__body .post-create__container .post-create-body .btn-file, .app__body .post-create__container .post-create-footer .msg-typing, .app__body .suggestion-list__content .command, .app__body .modal .modal-content, .app__body .dropdown-menu, .app__body .popover, .app__body .mentions__name, .app__body .tip-overlay, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'color:' + theme.centerChannelColor, 1);
- changeCss('.app__body .post .post__link', 'color:' + changeOpacity(theme.centerChannelColor, 0.65), 1);
- changeCss('.app__body #archive-link-home, .video-div .video-thumbnail__error', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
- changeCss('.app__body #post-create', 'color:' + theme.centerChannelColor, 2);
- changeCss('.app__body .mentions--top, .app__body .suggestion-list', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
- changeCss('.app__body .mentions--top, .app__body .suggestion-list', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
- changeCss('.app__body .mentions--top, .app__body .suggestion-list', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1);
- changeCss('.app__body .dropdown-menu, .app__body .popover ', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 3);
- changeCss('.app__body .dropdown-menu, .app__body .popover ', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 2);
- changeCss('.app__body .dropdown-menu, .app__body .popover ', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px', 1);
- changeCss('.app__body .post__body hr, .app__body .loading-screen .loading__content .round, .app__body .tutorial__circles .circle', 'background:' + theme.centerChannelColor, 1);
- changeCss('.app__body .channel-header .heading', 'color:' + theme.centerChannelColor, 1);
- changeCss('.app__body .markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
- changeCss('.app__body .channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
- changeCss('.app__body .channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
- changeCss('.app__body .custom-textarea, .app__body .custom-textarea:focus, .app__body .file-preview, .app__body .post-image__details, .app__body .sidebar--right .sidebar-right__body, .app__body .markdown__table th, .app__body .markdown__table td, .app__body .suggestion-list__content, .app__body .modal .modal-content, .app__body .modal .settings-modal .settings-table .settings-content .divider-light, .app__body .webhooks__container, .app__body .dropdown-menu, .app__body .modal .modal-header, .app__body .popover', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .popover.bottom>.arrow', 'border-bottom-color:' + changeOpacity(theme.centerChannelColor, 0.25), 1);
- changeCss('.app__body .search-help-popover .search-autocomplete__divider span, .app__body .suggestion-list__divider > span', 'color:' + changeOpacity(theme.centerChannelColor, 0.7), 1);
- changeCss('.app__body .popover.right>.arrow', 'border-right-color:' + changeOpacity(theme.centerChannelColor, 0.25), 1);
- changeCss('.app__body .popover.left>.arrow', 'border-left-color:' + changeOpacity(theme.centerChannelColor, 0.25), 1);
- changeCss('.app__body .popover.top>.arrow', 'border-top-color:' + changeOpacity(theme.centerChannelColor, 0.25), 1);
- changeCss('.app__body .suggestion-list__content .command, .app__body .popover .popover-title', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .suggestion-list__divider:before, .app__body .dropdown-menu .divider, .app__body .search-help-popover .search-autocomplete__divider:before', 'background:' + theme.centerChannelColor, 1);
- changeCss('.app__body .custom-textarea', 'color:' + theme.centerChannelColor, 1);
- changeCss('.app__body .post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
- changeCss('.app__body .post-image__details', 'color:' + theme.centerChannelColor, 2);
- changeCss('.app__body .post-image__column a, .app__body .post-image__column a:hover, .app__body .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1);
- changeCss('@media(min-width: 768px){.app__body .search-bar__container .search__form .search-bar, .app__body .form-control', 'color:' + theme.centerChannelColor, 2);
- changeCss('.app__body .input-group-addon, .app__body .search-bar__container .search__form, .app__body .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('@media(min-width: 768px){.app__body .post-list__table .post-list__content .dropdown-menu a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
- changeCss('.app__body .form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
- changeCss('.app__body .attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
- changeCss('.app__body .input-group-addon, .app__body .channel-intro .channel-intro__content, .app__body .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
- changeCss('.app__body .date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
- changeCss('.app__body .date-separator .separator__hr, .app__body .modal-footer, .app__body .modal .custom-textarea', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .search-item-container, .app__body .post-right__container .post.post--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
- changeCss('.app__body .modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
- changeCss('.app__body .channel-intro, .app__body .modal .settings-modal .settings-table .settings-content .divider-dark, .app__body hr, .app__body .modal .settings-modal .settings-table .settings-links, .app__body .modal .settings-modal .settings-table .settings-content .appearance-section .theme-elements__header', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .post.current--user .post__body, .app__body .post.post--comment.other--root.current--user .post-comment, .app__body pre, .app__body .post-right__container .post.post--root', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
- changeCss('.app__body .post.post--comment.other--root.current--user .post-comment, .app__body .more-modal__list .more-modal__row, .app__body .member-div:first-child, .app__body .member-div, .app__body .access-history__table .access__report, .app__body .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2);
- changeCss('@media(max-width: 1800px){.app__body .inner-wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
- changeCss('.app__body .post.post--hovered', 'background:' + changeOpacity(theme.centerChannelColor, 0.08), 1);
- changeCss('@media(min-width: 768px){.app__body .post:hover, .app__body .more-modal__list .more-modal__row:hover, .app__body .modal .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.08), 1);
- changeCss('.app__body .date-separator.hovered--before:after, .app__body .date-separator.hovered--after:before, .app__body .new-separator.hovered--after:before, .app__body .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
- changeCss('@media(min-width: 768px){.app__body .suggestion-list__content .command:hover, .app__body .mentions__name:hover, .app__body .suggestion--selected, .app__body .dropdown-menu>li>a:focus, .app__body .dropdown-menu>li>a:hover, .app__body .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
- changeCss('code, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
- changeCss('@media(min-width: 960px){.app__body .post.current--user:hover .post__body ', 'background: none;', 1);
- changeCss('.app__body .sidebar--right', 'color:' + theme.centerChannelColor, 2);
- changeCss('.app__body .search-help-popover .search-autocomplete__item:hover, .app__body .modal .settings-modal .settings-table .settings-content .appearance-section .theme-elements__body', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
- changeCss('.app__body .search-help-popover .search-autocomplete__item.selected', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
- changeCss('::-webkit-scrollbar-thumb', 'background:' + changeOpacity(theme.centerChannelColor, 0.4), 1);
- changeCss('body', 'scrollbar-arrow-color:' + theme.centerChannelColor, 4);
- changeCss('.app__body .post-create__container .post-create-body .btn-file svg, .app__body .post.post--compact .post-image__column .post-image__details svg, .app__body .modal .about-modal .about-modal__logo svg, .app__body .post .post__img svg', 'fill:' + theme.centerChannelColor, 1);
- changeCss('.app__body .scrollbar--horizontal, .app__body .scrollbar--vertical', 'background:' + changeOpacity(theme.centerChannelColor, 0.5), 2);
- changeCss('.app__body .post-list__new-messages-below', 'background:' + changeColor(theme.centerChannelColor, 0.5), 2);
- changeCss('.app__body .post.post--comment .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('@media(min-width: 768px){.app__body .post.post--compact.same--root.post--comment .post__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .post.post--comment.current--user .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.app__body .channel-header__info .status .offline--icon', 'fill:' + theme.centerChannelColor, 1);
- changeCss('.app__body .navbar .status .offline--icon', 'fill:' + theme.centerChannelColor, 1);
+ changeCss('.app__body .post-list__arrows, .app__body .post .flag-icon__container', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3));
+ changeCss('.app__body .modal .status .offline--icon, .app__body .channel-header__links .icon, .app__body .sidebar--right .sidebar--right__subheader .usage__icon', 'fill:' + theme.centerChannelColor);
+ changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .post .dropdown-menu a, .sidebar--left, .app__body .sidebar--right .sidebar--right__header, .app__body .suggestion-list__content .command', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .post.post--system .post__body', 'color:' + changeOpacity(theme.centerChannelColor, 0.6));
+ changeCss('.app__body .input-group-addon, .app__body .app__content, .app__body .post-create__container .post-create-body .btn-file, .app__body .post-create__container .post-create-footer .msg-typing, .app__body .suggestion-list__content .command, .app__body .modal .modal-content, .app__body .dropdown-menu, .app__body .popover, .app__body .mentions__name, .app__body .tip-overlay, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .post .post__link', 'color:' + changeOpacity(theme.centerChannelColor, 0.65));
+ changeCss('.app__body #archive-link-home, .video-div .video-thumbnail__error', 'background:' + changeOpacity(theme.centerChannelColor, 0.15));
+ changeCss('.app__body #post-create', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .mentions--top, .app__body .suggestion-list', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px');
+ changeCss('.app__body .mentions--top, .app__body .suggestion-list', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px');
+ changeCss('.app__body .mentions--top, .app__body .suggestion-list', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px');
+ changeCss('.app__body .dropdown-menu, .app__body .popover ', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px');
+ changeCss('.app__body .dropdown-menu, .app__body .popover ', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px');
+ changeCss('.app__body .dropdown-menu, .app__body .popover ', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.1) + ' 0px 6px 12px');
+ changeCss('.app__body .post__body hr, .app__body .loading-screen .loading__content .round, .app__body .tutorial__circles .circle', 'background:' + theme.centerChannelColor);
+ changeCss('.app__body .channel-header .heading', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07));
+ changeCss('.app__body .channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8));
+ changeCss('.app__body .channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8));
+ changeCss('.app__body .custom-textarea, .app__body .custom-textarea:focus, .app__body .file-preview, .app__body .post-image__details, .app__body .sidebar--right .sidebar-right__body, .app__body .markdown__table th, .app__body .markdown__table td, .app__body .suggestion-list__content, .app__body .modal .modal-content, .app__body .modal .settings-modal .settings-table .settings-content .divider-light, .app__body .webhooks__container, .app__body .dropdown-menu, .app__body .modal .modal-header, .app__body .popover', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .popover.bottom>.arrow', 'border-bottom-color:' + changeOpacity(theme.centerChannelColor, 0.25));
+ changeCss('.app__body .search-help-popover .search-autocomplete__divider span, .app__body .suggestion-list__divider > span', 'color:' + changeOpacity(theme.centerChannelColor, 0.7));
+ changeCss('.app__body .popover.right>.arrow', 'border-right-color:' + changeOpacity(theme.centerChannelColor, 0.25));
+ changeCss('.app__body .popover.left>.arrow', 'border-left-color:' + changeOpacity(theme.centerChannelColor, 0.25));
+ changeCss('.app__body .popover.top>.arrow', 'border-top-color:' + changeOpacity(theme.centerChannelColor, 0.25));
+ changeCss('.app__body .suggestion-list__content .command, .app__body .popover .popover-title', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .suggestion-list__divider:before, .app__body .dropdown-menu .divider, .app__body .search-help-popover .search-autocomplete__divider:before', 'background:' + theme.centerChannelColor);
+ changeCss('.app__body .custom-textarea', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .post-image__details', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .post-image__column a, .app__body .post-image__column a:hover, .app__body .post-image__column a:focus', 'color:' + theme.centerChannelColor);
+ changeCss('@media(min-width: 768px){.app__body .search-bar__container .search__form .search-bar, .app__body .form-control', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .input-group-addon, .app__body .search-bar__container .search__form, .app__body .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('@media(min-width: 768px){.app__body .post-list__table .post-list__content .dropdown-menu a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.1));
+ changeCss('.app__body .form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3));
+ changeCss('.app__body .attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3));
+ changeCss('.app__body .input-group-addon, .app__body .channel-intro .channel-intro__content, .app__body .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05));
+ changeCss('.app__body .date-separator .separator__text', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .date-separator .separator__hr, .app__body .modal-footer, .app__body .modal .custom-textarea', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .search-item-container, .app__body .post-right__container .post.post--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1));
+ changeCss('.app__body .modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3));
+ changeCss('.app__body .channel-intro, .app__body .modal .settings-modal .settings-table .settings-content .divider-dark, .app__body hr, .app__body .modal .settings-modal .settings-table .settings-links, .app__body .modal .settings-modal .settings-table .settings-content .appearance-section .theme-elements__header', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .post.current--user .post__body, .app__body .post.post--comment.other--root.current--user .post-comment, .app__body pre, .app__body .post-right__container .post.post--root', 'background:' + changeOpacity(theme.centerChannelColor, 0.05));
+ changeCss('.app__body .post.post--comment.other--root.current--user .post-comment, .app__body .more-modal__list .more-modal__row, .app__body .member-div:first-child, .app__body .member-div, .app__body .access-history__table .access__report, .app__body .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1));
+ changeCss('@media(max-width: 1800px){.app__body .inner-wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07));
+ changeCss('.app__body .post.post--hovered', 'background:' + changeOpacity(theme.centerChannelColor, 0.08));
+ changeCss('@media(min-width: 768px){.app__body .post:hover, .app__body .more-modal__list .more-modal__row:hover, .app__body .modal .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.08));
+ changeCss('.app__body .date-separator.hovered--before:after, .app__body .date-separator.hovered--after:before, .app__body .new-separator.hovered--after:before, .app__body .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07));
+ changeCss('@media(min-width: 768px){.app__body .suggestion-list__content .command:hover, .app__body .mentions__name:hover, .app__body .dropdown-menu>li>a:focus, .app__body .dropdown-menu>li>a:hover, .app__body .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15));
+ changeCss('.app__body .suggestion--selected', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
+ changeCss('code, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'background:' + changeOpacity(theme.centerChannelColor, 0.1));
+ changeCss('@media(min-width: 960px){.app__body .post.current--user:hover .post__body ', 'background: none;');
+ changeCss('.app__body .sidebar--right', 'color:' + theme.centerChannelColor);
+ changeCss('.app__body .search-help-popover .search-autocomplete__item:hover, .app__body .modal .settings-modal .settings-table .settings-content .appearance-section .theme-elements__body', 'background:' + changeOpacity(theme.centerChannelColor, 0.05));
+ changeCss('.app__body .search-help-popover .search-autocomplete__item.selected', 'background:' + changeOpacity(theme.centerChannelColor, 0.15));
+ changeCss('body.app__body ::-webkit-scrollbar-thumb', 'background:' + changeOpacity(theme.centerChannelColor, 0.4), 1);
+ changeCss('body', 'scrollbar-arrow-color:' + theme.centerChannelColor);
+ changeCss('.app__body .post-create__container .post-create-body .btn-file svg, .app__body .post.post--compact .post-image__column .post-image__details svg, .app__body .modal .about-modal .about-modal__logo svg, .app__body .post .post__img svg', 'fill:' + theme.centerChannelColor);
+ changeCss('.app__body .scrollbar--horizontal, .app__body .scrollbar--vertical', 'background:' + changeOpacity(theme.centerChannelColor, 0.5));
+ changeCss('.app__body .post-list__new-messages-below', 'background:' + changeColor(theme.centerChannelColor, 0.5));
+ changeCss('.app__body .post.post--comment .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('@media(min-width: 768px){.app__body .post.post--compact.same--root.post--comment .post__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .post.post--comment.current--user .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .channel-header__info .status .offline--icon', 'fill:' + theme.centerChannelColor);
+ changeCss('.app__body .navbar .status .offline--icon', 'fill:' + theme.centerChannelColor);
}
if (theme.newMessageSeparator) {
- changeCss('.app__body .new-separator .separator__text', 'color:' + theme.newMessageSeparator, 1);
- changeCss('.app__body .new-separator .separator__hr', 'border-color:' + changeOpacity(theme.newMessageSeparator, 0.5), 1);
+ changeCss('.app__body .new-separator .separator__text', 'color:' + theme.newMessageSeparator);
+ changeCss('.app__body .new-separator .separator__hr', 'border-color:' + changeOpacity(theme.newMessageSeparator, 0.5));
}
if (theme.linkColor) {
- changeCss('.app__body a, .app__body a:focus, .app__body a:hover, .app__body .btn, .app__body .btn:focus, .app__body .btn:hover', 'color:' + theme.linkColor, 1);
- changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + changeOpacity(theme.linkColor, 0.5), 1);
- changeCss('.app__body .channel-header__links .icon:hover, .app__body .post .flag-icon__container.visible, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor, 1);
+ changeCss('.app__body a, .app__body a:focus, .app__body a:hover, .app__body .btn, .app__body .btn:focus, .app__body .btn:hover', 'color:' + theme.linkColor);
+ changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + changeOpacity(theme.linkColor, 0.5));
+ changeCss('.app__body .channel-header__links .icon:hover, .app__body .post .flag-icon__container.visible, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor);
}
if (theme.buttonBg) {
- changeCss('.app__body .btn.btn-primary, .app__body .tutorial__circles .circle.active', 'background:' + theme.buttonBg, 1);
- changeCss('.app__body .btn.btn-primary:hover, .app__body .btn.btn-primary:active, .app__body .btn.btn-primary:focus', 'background:' + changeColor(theme.buttonBg, -0.25), 1);
+ changeCss('.app__body .btn.btn-primary, .app__body .tutorial__circles .circle.active', 'background:' + theme.buttonBg);
+ changeCss('.app__body .btn.btn-primary:hover, .app__body .btn.btn-primary:active, .app__body .btn.btn-primary:focus', 'background:' + changeColor(theme.buttonBg, -0.25));
}
if (theme.buttonColor) {
- changeCss('.app__body .btn.btn-primary', 'color:' + theme.buttonColor, 2);
+ changeCss('.app__body .btn.btn-primary', 'color:' + theme.buttonColor);
}
if (theme.mentionHighlightBg) {
- changeCss('.app__body .mention--highlight, .app__body .search-highlight', 'background:' + theme.mentionHighlightBg, 1);
- changeCss('.mention-comment', 'border-color:' + theme.mentionHighlightBg + ' !important', 1);
- changeCss('.app__body .post.post--highlight', 'background:' + changeOpacity(theme.mentionHighlightBg, 0.5), 1);
+ changeCss('.app__body .mention--highlight, .app__body .search-highlight', 'background:' + theme.mentionHighlightBg);
+ changeCss('.mention-comment', 'border-color:' + theme.mentionHighlightBg + ' !important');
+ changeCss('.app__body .post.post--highlight', 'background:' + changeOpacity(theme.mentionHighlightBg, 0.5));
}
if (theme.mentionHighlightLink) {
- changeCss('.app__body .mention--highlight .mention-link, .app__body .mention--highlight, .app__body .search-highlight', 'color:' + theme.mentionHighlightLink, 1);
+ changeCss('.app__body .mention--highlight .mention-link, .app__body .mention--highlight, .app__body .search-highlight', 'color:' + theme.mentionHighlightLink);
}
if (!theme.codeTheme) {
@@ -686,24 +686,19 @@ export function applyFont(fontName) {
}
}
-export function changeCss(className, classValue, classRepeat) {
- // we need invisible container to store additional css definitions
- var cssMainContainer = $('#css-modifier-container');
- if (cssMainContainer.length === 0) {
- cssMainContainer = $('<div id="css-modifier-container"></div>');
- cssMainContainer.hide();
- cssMainContainer.appendTo($('body'));
+export function changeCss(className, classValue) {
+ let styleEl = document.querySelector('style[data-class="' + className + classValue + '"]');
+ if (!styleEl) {
+ styleEl = document.createElement('style');
+ styleEl.setAttribute('data-class', className);
}
- // and we need one div for each class
- var classContainer = cssMainContainer.find('div[data-class="' + className + classRepeat + '"]');
- if (classContainer.length === 0) {
- classContainer = $('<div data-class="' + className + classRepeat + '"></div>');
- classContainer.appendTo(cssMainContainer);
- }
+ // Append style element to head
+ document.head.appendChild(styleEl);
- // append additional style
- classContainer.html('<style>' + className + ' {' + classValue + '}</style>');
+ // Grab style sheet
+ var styleSheet = styleEl.sheet;
+ styleSheet.insertRule(className + '{' + classValue + '}', styleSheet.cssRules.length);
}
export function updateCodeTheme(userTheme) {
@@ -713,7 +708,6 @@ export function updateCodeTheme(userTheme) {
element.themes.forEach((theme) => {
if (userTheme === theme.id) {
cssPath = theme.cssURL;
- return;
}
});
}
@@ -741,8 +735,6 @@ export function placeCaretAtEnd(el) {
el.focus();
el.selectionStart = el.value.length;
el.selectionEnd = el.value.length;
-
- return;
}
export function getCaretPosition(el) {
@@ -804,7 +796,7 @@ export function isValidUsername(name) {
error = 'This field is required';
} else if (name.length < Constants.MIN_USERNAME_LENGTH || name.length > Constants.MAX_USERNAME_LENGTH) {
error = 'Must be between ' + Constants.MIN_USERNAME_LENGTH + ' and ' + Constants.MAX_USERNAME_LENGTH + ' characters';
- } else if (!(/^[a-z0-9\.\-_]+$/).test(name)) {
+ } else if (!(/^[a-z0-9.\-_]+$/).test(name)) {
error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'.";
} else if (!(/[a-z]/).test(name.charAt(0))) { //eslint-disable-line no-negated-condition
error = 'First character must be a letter.';
diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js
index 4058abbd2..f1742e3ae 100644
--- a/webapp/webpack.config.js
+++ b/webapp/webpack.config.js
@@ -34,7 +34,7 @@ var config = {
loaders: [
{
test: /\.jsx?$/,
- loader: 'babel',
+ loader: 'babel-loader',
exclude: /(node_modules|non_npm_dependencies)/,
query: {
presets: [
@@ -49,15 +49,15 @@ var config = {
{
test: /\.json$/,
exclude: /manifest\.json$/,
- loader: 'json'
+ loader: 'json-loader'
},
{
test: /manifest\.json$/,
- loader: 'file?name=files/[hash].[ext]'
+ loader: 'file-loader?name=files/[hash].[ext]'
},
{
test: /(node_modules|non_npm_dependencies)(\\|\/).+\.(js|jsx)$/,
- loader: 'imports',
+ loader: 'imports-loader',
query: {
$: 'jquery',
jQuery: 'jquery'
@@ -65,22 +65,22 @@ var config = {
},
{
test: /\.scss$/,
- loaders: ['style', 'css', 'sass']
+ loaders: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.css$/,
- loaders: ['style', 'css']
+ loaders: ['style-loader', 'css-loader']
},
{
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|gif|mp3|jpg)$/,
loaders: [
- 'file?name=files/[hash].[ext]',
- 'image-webpack'
+ 'file-loader?name=files/[hash].[ext]',
+ 'image-webpack-loader'
]
},
{
test: /\.html$/,
- loader: 'html?attrs=link:href'
+ loader: 'html-loader?attrs=link:href'
}
]
},
@@ -88,13 +88,6 @@ var config = {
new webpack.ProvidePlugin({
'window.jQuery': 'jquery'
}),
- new CopyWebpackPlugin([
- {from: 'images/emoji', to: 'emoji'},
- {from: 'images/logo-email.png', to: 'images'},
- {from: 'images/circles.png', to: 'images'},
- {from: 'images/favicon', to: 'images/favicon'},
- {from: 'images/appIcons.png', to: 'images'}
- ]),
new webpack.LoaderOptionsPlugin({
minimize: !DEV,
debug: false
@@ -106,11 +99,6 @@ var config = {
}
}
}),
- new webpack.DefinePlugin({
- 'process.env': {
- NODE_ENV: JSON.stringify('production')
- }
- }),
new webpack.optimize.CommonsChunkPlugin({
minChunks: 2,
children: true
@@ -150,14 +138,19 @@ if (!DEV) {
compress: {
warnings: false
},
- comments: false
+ comments: false,
+ sourceMap: true
})
);
config.plugins.push(
new webpack.optimize.OccurrenceOrderPlugin(true)
);
config.plugins.push(
- new webpack.optimize.DedupePlugin()
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('production')
+ }
+ })
);
}
@@ -167,7 +160,7 @@ if (TEST) {
config.target = 'node';
config.externals = [nodeExternals()];
} else {
- // For some reason this breaks mocha. So it goes here.
+ // For some reason these break mocha. So they go here.
config.plugins.push(
new HtmlWebpackPlugin({
filename: 'root.html',
@@ -175,6 +168,15 @@ if (TEST) {
template: 'root.html'
})
);
+ config.plugins.push(
+ new CopyWebpackPlugin([
+ {from: 'images/emoji', to: 'emoji'},
+ {from: 'images/logo-email.png', to: 'images'},
+ {from: 'images/circles.png', to: 'images'},
+ {from: 'images/favicon', to: 'images/favicon'},
+ {from: 'images/appIcons.png', to: 'images'}
+ ])
+ );
}
module.exports = config;