summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/action_creators/global_actions.jsx (renamed from web/react/dispatcher/event_helpers.jsx)30
-rw-r--r--web/react/components/about_build_modal.jsx39
-rw-r--r--web/react/components/activity_log_modal.jsx36
-rw-r--r--web/react/components/admin_console/admin_controller.jsx11
-rw-r--r--web/react/components/admin_console/admin_navbar_dropdown.jsx19
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx4
-rw-r--r--web/react/components/admin_console/admin_sidebar_header.jsx5
-rw-r--r--web/react/components/admin_console/ldap_settings.jsx6
-rw-r--r--web/react/components/admin_console/license_settings.jsx53
-rw-r--r--web/react/components/admin_console/user_item.jsx2
-rw-r--r--web/react/components/audit_table.jsx21
-rw-r--r--web/react/components/center_panel.jsx47
-rw-r--r--web/react/components/channel_header.jsx32
-rw-r--r--web/react/components/channel_invite_modal.jsx37
-rw-r--r--web/react/components/channel_loader.jsx204
-rw-r--r--web/react/components/channel_notifications_modal.jsx107
-rw-r--r--web/react/components/channel_view.jsx26
-rw-r--r--web/react/components/claim/claim_account.jsx87
-rw-r--r--web/react/components/claim/sso_to_email.jsx2
-rw-r--r--web/react/components/create_post.jsx6
-rw-r--r--web/react/components/delete_channel_modal.jsx4
-rw-r--r--web/react/components/do_verify_email.jsx82
-rw-r--r--web/react/components/docs.jsx41
-rw-r--r--web/react/components/edit_post_modal.jsx4
-rw-r--r--web/react/components/email_verify.jsx108
-rw-r--r--web/react/components/file_attachment.jsx6
-rw-r--r--web/react/components/find_team.jsx135
-rw-r--r--web/react/components/invite_member_modal.jsx4
-rw-r--r--web/react/components/logged_in.jsx224
-rw-r--r--web/react/components/login.jsx248
-rw-r--r--web/react/components/login_email.jsx11
-rw-r--r--web/react/components/navbar.jsx20
-rw-r--r--web/react/components/navbar_dropdown.jsx88
-rw-r--r--web/react/components/needs_team.jsx20
-rw-r--r--web/react/components/not_logged_in.jsx70
-rw-r--r--web/react/components/password_reset.jsx47
-rw-r--r--web/react/components/password_reset_form.jsx105
-rw-r--r--web/react/components/password_reset_send_link.jsx186
-rw-r--r--web/react/components/popover_list_members.jsx2
-rw-r--r--web/react/components/post.jsx21
-rw-r--r--web/react/components/post_body.jsx13
-rw-r--r--web/react/components/post_body_additional_content.jsx20
-rw-r--r--web/react/components/post_focus_view.jsx22
-rw-r--r--web/react/components/post_header.jsx17
-rw-r--r--web/react/components/post_info.jsx56
-rw-r--r--web/react/components/posts_view.jsx18
-rw-r--r--web/react/components/posts_view_container.jsx29
-rw-r--r--web/react/components/rhs_comment.jsx8
-rw-r--r--web/react/components/rhs_root_post.jsx8
-rw-r--r--web/react/components/root.jsx90
-rw-r--r--web/react/components/search_results_item.jsx10
-rw-r--r--web/react/components/should_verify_email.jsx111
-rw-r--r--web/react/components/sidebar.jsx30
-rw-r--r--web/react/components/sidebar_header.jsx14
-rw-r--r--web/react/components/sidebar_right.jsx9
-rw-r--r--web/react/components/sidebar_right_menu.jsx39
-rw-r--r--web/react/components/signup_team.jsx159
-rw-r--r--web/react/components/signup_team_complete.jsx121
-rw-r--r--web/react/components/signup_team_complete/components/signup_team_complete.jsx79
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx (renamed from web/react/components/team_signup_display_name_page.jsx)6
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_email_item.jsx (renamed from web/react/components/team_signup_email_item.jsx)2
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_finished.jsx15
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_password_page.jsx (renamed from web/react/components/team_signup_password_page.jsx)15
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx (renamed from web/react/components/team_signup_send_invites_page.jsx)2
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_url_page.jsx (renamed from web/react/components/team_signup_url_page.jsx)8
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_username_page.jsx (renamed from web/react/components/team_signup_username_page.jsx)8
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx (renamed from web/react/components/team_signup_welcome_page.jsx)12
-rw-r--r--web/react/components/signup_team_confirm.jsx47
-rw-r--r--web/react/components/signup_user_complete.jsx331
-rw-r--r--web/react/components/suggestion/at_mention_provider.jsx2
-rw-r--r--web/react/components/suggestion/suggestion_box.jsx12
-rw-r--r--web/react/components/suggestion/suggestion_list.jsx4
-rw-r--r--web/react/components/team_members_modal.jsx32
-rw-r--r--web/react/components/team_settings.jsx3
-rw-r--r--web/react/components/team_signup_with_email.jsx7
-rw-r--r--web/react/components/textbox.jsx26
-rw-r--r--web/react/components/user_list_row.jsx2
-rw-r--r--web/react/components/user_profile.jsx29
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx2
-rw-r--r--web/react/components/user_settings/manage_languages.jsx3
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx4
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx15
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx16
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx29
-rw-r--r--web/react/components/view_image_popover_bar.jsx1
-rw-r--r--web/react/package.json2
-rw-r--r--web/react/pages/admin_console.jsx71
-rw-r--r--web/react/pages/channel.jsx97
-rw-r--r--web/react/pages/claim_account.jsx68
-rw-r--r--web/react/pages/docs.jsx64
-rw-r--r--web/react/pages/find_team.jsx62
-rw-r--r--web/react/pages/home.jsx16
-rw-r--r--web/react/pages/login.jsx66
-rw-r--r--web/react/pages/password_reset.jsx68
-rw-r--r--web/react/pages/root.jsx290
-rw-r--r--web/react/pages/signup_team.jsx76
-rw-r--r--web/react/pages/signup_team_complete.jsx66
-rw-r--r--web/react/pages/signup_team_confirm.jsx64
-rw-r--r--web/react/pages/signup_user_complete.jsx69
-rw-r--r--web/react/pages/verify.jsx67
-rw-r--r--web/react/stores/admin_store.jsx6
-rw-r--r--web/react/stores/browser_store.jsx6
-rw-r--r--web/react/stores/localization_store.jsx60
-rw-r--r--web/react/stores/socket_store.jsx28
-rw-r--r--web/react/stores/team_store.jsx38
-rw-r--r--web/react/stores/user_store.jsx62
-rw-r--r--web/react/utils/async_client.jsx55
-rw-r--r--web/react/utils/channel_intro_messages.jsx49
-rw-r--r--web/react/utils/client.jsx179
-rw-r--r--web/react/utils/constants.jsx41
-rw-r--r--web/react/utils/markdown.jsx25
-rw-r--r--web/react/utils/utils.jsx33
-rw-r--r--web/sass-files/sass/partials/_admin-console.scss19
-rw-r--r--web/sass-files/sass/partials/_post.scss31
-rw-r--r--web/sass-files/sass/partials/_responsive.scss3
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss6
-rw-r--r--web/static/help/Messaging_en.md47
-rw-r--r--web/static/help/Messaging_es.md37
-rw-r--r--web/static/i18n/en.json28
-rw-r--r--web/static/i18n/es.json25
-rw-r--r--web/static/i18n/pt.json53
-rw-r--r--web/static/images/themes/code_themes/solarized-dark.png (renamed from web/static/images/themes/code_themes/solarized_dark.png)bin81942 -> 81942 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized-light.png (renamed from web/static/images/themes/code_themes/solarized_light.png)bin82868 -> 82868 bytes
-rw-r--r--web/templates/admin_console.html21
-rw-r--r--web/templates/authorize.html12
-rw-r--r--web/templates/channel.html21
-rw-r--r--web/templates/claim_account.html30
-rw-r--r--web/templates/docs.html27
-rw-r--r--web/templates/find_team.html30
-rw-r--r--web/templates/footer.html39
-rw-r--r--web/templates/head.html191
-rw-r--r--web/templates/home.html24
-rw-r--r--web/templates/login.html27
-rw-r--r--web/templates/password_reset.html30
-rw-r--r--web/templates/signup_team.html29
-rw-r--r--web/templates/signup_team_complete.html29
-rw-r--r--web/templates/signup_team_confirm.html26
-rw-r--r--web/templates/signup_user_complete.html29
-rw-r--r--web/templates/verify.html30
-rw-r--r--web/web.go1158
140 files changed, 2793 insertions, 4623 deletions
diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/action_creators/global_actions.jsx
index 367347d4b..4375d6c87 100644
--- a/web/react/dispatcher/event_helpers.jsx
+++ b/web/react/action_creators/global_actions.jsx
@@ -220,3 +220,33 @@ export function sendEphemeralPost(message, channelId) {
emitPostRecievedEvent(post);
}
+
+export function loadTeamRequiredPage() {
+ AsyncClient.getAllTeams();
+}
+
+export function newLocalizationSelected(locale) {
+ Client.getTranslations(
+ locale,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_LOCALE,
+ locale,
+ translations: data
+ });
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getTranslations');
+ }
+ );
+}
+
+export function viewLoggedIn() {
+ AsyncClient.getChannels();
+ AsyncClient.getChannelExtraInfo();
+ AsyncClient.getMyTeam();
+ AsyncClient.getMe();
+
+ // Clear pending posts (shouldn't have pending posts if we are loading)
+ PostStore.clearPendingPosts();
+}
diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx
index fe48bb48e..34b1fdccf 100644
--- a/web/react/components/about_build_modal.jsx
+++ b/web/react/components/about_build_modal.jsx
@@ -21,29 +21,38 @@ export default class AboutBuildModal extends React.Component {
let title = (
<FormattedMessage
- id='about.teamEdtion'
- defaultMessage='Team Edition'
+ id='about.teamEditiont0'
+ defaultMessage='Team Edition T0'
/>
);
+
let licensee;
- if (config.BuildEnterpriseReady === 'true' && license.IsLicensed === 'true') {
+ if (config.BuildEnterpriseReady === 'true') {
title = (
<FormattedMessage
- id='about.enterpriseEdition'
- defaultMessage='Enterprise Edition'
+ id='about.teamEditiont1'
+ defaultMessage='Team Edition T1'
/>
);
- licensee = (
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>
- <FormattedMessage
- id='about.licensed'
- defaultMessage='Licensed by:'
- />
+ if (license.IsLicensed === 'true') {
+ title = (
+ <FormattedMessage
+ id='about.enterpriseEditione1'
+ defaultMessage='Enterprise Edition E1'
+ />
+ );
+ licensee = (
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>
+ <FormattedMessage
+ id='about.licensed'
+ defaultMessage='Licensed by:'
+ />
+ </div>
+ <div className='col-sm-9'>{license.Company}</div>
</div>
- <div className='col-sm-9'>{license.Company}</div>
- </div>
- );
+ );
+ }
}
return (
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 95b4caa12..db366f8ed 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -8,7 +8,7 @@ const Modal = ReactBootstrap.Modal;
import LoadingScreen from './loading_screen.jsx';
import * as Utils from '../utils/utils.jsx';
-import {FormattedMessage} from 'mm-intl';
+import {FormattedMessage, FormattedTime, FormattedDate} from 'mm-intl';
export default class ActivityLogModal extends React.Component {
constructor(props) {
@@ -144,8 +144,21 @@ export default class ActivityLogModal extends React.Component {
id='activity_log.firstTime'
defaultMessage='First time active: {date}, {time}'
values={{
- date: firstAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ date: (
+ <FormattedDate
+ value={firstAccessTime}
+ day='2-digit'
+ month='long'
+ year='numeric'
+ />
+ ),
+ time: (
+ <FormattedTime
+ value={firstAccessTime}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ )
}}
/>
</div>
@@ -206,8 +219,21 @@ export default class ActivityLogModal extends React.Component {
id='activity_log.lastActivity'
defaultMessage='Last activity: {date}, {time}'
values={{
- date: lastAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ date: (
+ <FormattedDate
+ value={lastAccessTime}
+ day='2-digit'
+ month='long'
+ year='numeric'
+ />
+ ),
+ time: (
+ <FormattedTime
+ value={lastAccessTime}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ )
}}
/>
</div>
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 32ed70a99..4c4f21f08 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -6,7 +6,6 @@ import AdminStore from '../../stores/admin_store.jsx';
import TeamStore from '../../stores/team_store.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import LoadingScreen from '../loading_screen.jsx';
-import * as Utils from '../../utils/utils.jsx';
import EmailSettingsTab from './email_settings.jsx';
import LogSettingsTab from './log_settings.jsx';
@@ -50,11 +49,6 @@ export default class AdminController extends React.Component {
selected: props.tab || 'system_analytics',
selectedTeam: props.teamId || null
};
-
- if (!props.tab) {
- var tokenIndex = Utils.getUrlParameter('session_token_index');
- history.replaceState(null, null, `/admin_console/${this.state.selected}?session_token_index=${tokenIndex}`);
- }
}
componentDidMount() {
@@ -63,6 +57,9 @@ export default class AdminController extends React.Component {
AdminStore.addAllTeamsChangeListener(this.onAllTeamsListenerChange);
AsyncClient.getAllTeams();
+
+ $('[data-toggle="tooltip"]').tooltip();
+ $('[data-toggle="popover"]').popover();
}
componentWillUnmount() {
@@ -175,7 +172,7 @@ export default class AdminController extends React.Component {
}
return (
- <div>
+ <div id='admin_controller'>
<div
className='sidebar--menu'
id='sidebar-menu'
diff --git a/web/react/components/admin_console/admin_navbar_dropdown.jsx b/web/react/components/admin_console/admin_navbar_dropdown.jsx
index dc0b3c4cb..ae95f5a3a 100644
--- a/web/react/components/admin_console/admin_navbar_dropdown.jsx
+++ b/web/react/components/admin_console/admin_navbar_dropdown.jsx
@@ -2,13 +2,14 @@
// See License.txt for license information.
import * as Utils from '../../utils/utils.jsx';
-import * as Client from '../../utils/client.jsx';
import TeamStore from '../../stores/team_store.jsx';
import Constants from '../../utils/constants.jsx';
import {FormattedMessage} from 'mm-intl';
+import {Link} from 'react-router';
+
function getStateFromStores() {
return {currentTeam: TeamStore.getCurrent()};
}
@@ -18,16 +19,9 @@ export default class AdminNavbarDropdown extends React.Component {
super(props);
this.blockToggle = false;
- this.handleLogoutClick = this.handleLogoutClick.bind(this);
-
this.state = getStateFromStores();
}
- handleLogoutClick(e) {
- e.preventDefault();
- Client.logout();
- }
-
componentDidMount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
this.blockToggle = true;
@@ -78,15 +72,12 @@ export default class AdminNavbarDropdown extends React.Component {
</a>
</li>
<li>
- <a
- href='#'
- onClick={this.handleLogoutClick}
- >
+ <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}>
<FormattedMessage
id='admin.nav.logout'
defaultMessage='Logout'
/>
- </a>
+ </Link>
</li>
<li className='divider'></li>
<li>
@@ -116,4 +107,4 @@ export default class AdminNavbarDropdown extends React.Component {
</ul>
);
}
-} \ No newline at end of file
+}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 6621e5743..c2f31f569 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -3,7 +3,6 @@
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import SelectTeamModal from './select_team_modal.jsx';
-import * as Utils from '../../utils/utils.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -30,8 +29,6 @@ export default class AdminSidebar extends React.Component {
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name, teamId);
- var tokenIndex = Utils.getUrlParameter('session_token_index');
- history.pushState({name, teamId}, null, `/admin_console/${name}/${teamId || ''}?session_token_index=${tokenIndex}`);
}
isSelected(name, teamId) {
@@ -73,7 +70,6 @@ export default class AdminSidebar extends React.Component {
}
teamSelectedModal(teamId) {
- this.props.selectedTeams[teamId] = 'true';
this.setState({showSelectModal: false});
this.props.addSelectedTeam(teamId);
this.forceUpdate();
diff --git a/web/react/components/admin_console/admin_sidebar_header.jsx b/web/react/components/admin_console/admin_sidebar_header.jsx
index 8c9f74934..f1281c6ee 100644
--- a/web/react/components/admin_console/admin_sidebar_header.jsx
+++ b/web/react/components/admin_console/admin_sidebar_header.jsx
@@ -3,7 +3,6 @@
import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from '../../stores/user_store.jsx';
-import * as Utils from '../../utils/utils.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -39,7 +38,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
/>
);
}
@@ -65,4 +64,4 @@ export default class SidebarHeader extends React.Component {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx
index 535c264dd..4cd19c886 100644
--- a/web/react/components/admin_console/ldap_settings.jsx
+++ b/web/react/components/admin_console/ldap_settings.jsx
@@ -20,7 +20,7 @@ var holders = defineMessages({
},
baseEx: {
id: 'admin.ldap.baseEx',
- defaultMessage: 'Ex "dc=mydomain,dc=com"'
+ defaultMessage: 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"'
},
firstnameAttrEx: {
id: 'admin.ldap.firstnameAttrEx',
@@ -32,7 +32,7 @@ var holders = defineMessages({
},
emailAttrEx: {
id: 'admin.ldap.emailAttrEx',
- defaultMessage: 'Ex "mail"'
+ defaultMessage: 'Ex "mail" or "userPrincipalName"'
},
usernameAttrEx: {
id: 'admin.ldap.usernameAttrEx',
@@ -581,4 +581,4 @@ LdapSettings.propTypes = {
config: React.PropTypes.object
};
-export default injectIntl(LdapSettings); \ No newline at end of file
+export default injectIntl(LdapSettings);
diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx
index d4dfa13f2..9d2ec8030 100644
--- a/web/react/components/admin_console/license_settings.jsx
+++ b/web/react/components/admin_console/license_settings.jsx
@@ -27,6 +27,7 @@ class LicenseSettings extends React.Component {
this.state = {
fileSelected: false,
+ fileName: null,
serverError: null
};
}
@@ -34,7 +35,7 @@ class LicenseSettings extends React.Component {
handleChange() {
const element = $(ReactDOM.findDOMNode(this.refs.fileInput));
if (element.prop('files').length > 0) {
- this.setState({fileSelected: true});
+ this.setState({fileSelected: true, fileName: element.prop('files')[0].name});
}
}
@@ -56,13 +57,13 @@ class LicenseSettings extends React.Component {
() => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
- this.setState({serverError: null});
+ this.setState({fileSelected: false, fileName: null, serverError: null});
window.location.reload(true);
},
(error) => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
- this.setState({serverError: error.message});
+ this.setState({fileSelected: false, fileName: null, serverError: error.message});
}
);
}
@@ -75,12 +76,12 @@ class LicenseSettings extends React.Component {
Client.removeLicenseFile(
() => {
$('#remove-button').button('reset');
- this.setState({serverError: null});
+ this.setState({fileSelected: false, fileName: null, serverError: null});
window.location.reload(true);
},
(error) => {
$('#remove-button').button('reset');
- this.setState({serverError: error.message});
+ this.setState({fileSelected: false, fileName: null, serverError: error.message});
}
);
}
@@ -172,17 +173,36 @@ class LicenseSettings extends React.Component {
/>
);
+ let fileName;
+ if (this.state.fileName) {
+ fileName = this.state.fileName;
+ } else {
+ fileName = (
+ <FormattedMessage
+ id='admin.license.noFile'
+ defaultMessage='No file uploaded'
+ />
+ );
+ }
+
licenseKey = (
<div className='col-sm-8'>
- <input
- className='pull-left'
- ref='fileInput'
- type='file'
- accept='.mattermost-license'
- onChange={this.handleChange}
- />
+ <div className='file__upload'>
+ <button className='btn btn-default'>
+ <FormattedMessage
+ id='admin.license.choose'
+ defaultMessage='Choose File'
+ />
+ </button>
+ <input
+ ref='fileInput'
+ type='file'
+ accept='.mattermost-license'
+ onChange={this.handleChange}
+ />
+ </div>
<button
- className={btnClass + ' pull-left'}
+ className={btnClass}
disabled={!this.state.fileSelected}
onClick={this.handleSubmit}
id='upload-button'
@@ -193,11 +213,12 @@ class LicenseSettings extends React.Component {
defaultMessage='Upload'
/>
</button>
- <br/>
- <br/>
+ <div className='help-text no-margin'>
+ {fileName}
+ </div>
<br/>
{serverError}
- <p className='help-text'>
+ <p className='help-text no-margin'>
<FormattedHTMLMessage
id='admin.license.uploadDesc'
defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a> to learn more about the benefits of Enterprise Edition or to purchase a key.'
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 4af350bcd..7d6cfb5c3 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -366,7 +366,7 @@ export default class UserItem extends React.Component {
<td className='row member-div padding--equal'>
<img
className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
height='36'
width='36'
/>
diff --git a/web/react/components/audit_table.jsx b/web/react/components/audit_table.jsx
index 47eee6d3f..917093840 100644
--- a/web/react/components/audit_table.jsx
+++ b/web/react/components/audit_table.jsx
@@ -5,7 +5,7 @@ import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import * as Utils from '../utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate, FormattedTime} from 'mm-intl';
const holders = defineMessages({
sessionRevoked: {
@@ -598,8 +598,23 @@ export function formatAuditInfo(audit, formatMessage) {
}
const date = new Date(audit.create_at);
- let auditInfo = {};
- auditInfo.timestamp = date.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' + date.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'});
+ const auditInfo = {};
+ auditInfo.timestamp = (
+ <div>
+ <FormattedDate
+ value={date}
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ {' - '}
+ <FormattedTime
+ value={date}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ </div>
+ );
auditInfo.userId = audit.user_id;
auditInfo.desc = auditDesc;
auditInfo.ip = audit.ip_address;
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 2422588cf..2ea840c1e 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -25,40 +25,43 @@ export default class CenterPanel extends React.Component {
constructor(props) {
super(props);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onChannelChange = this.onChannelChange.bind(this);
- this.onUserChange = this.onUserChange.bind(this);
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.validState = this.validState.bind(this);
+ this.onStoresChange = this.onStoresChange.bind(this);
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- this.state = {
- showTutorialScreens: tutorialStep === TutorialSteps.INTRO_SCREENS,
+ return {
+ showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS,
showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS,
user: UserStore.getCurrentUser(),
+ channel: ChannelStore.getCurrent(),
profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))
};
}
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- ChannelStore.addChangeListener(this.onChannelChange);
- UserStore.addChangeListener(this.onUserChange);
- }
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- ChannelStore.removeChangeListener(this.onChannelChange);
- UserStore.removeChangeListener(this.onUserChange);
+ validState() {
+ return this.state.user && this.state.channel && this.state.profiles;
}
- onPreferenceChange() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- this.setState({showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS});
+ onStoresChange() {
+ this.setState(this.getStateFromStores());
}
- onChannelChange() {
- this.setState({showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS});
+ componentDidMount() {
+ PreferenceStore.addChangeListener(this.onStoresChange);
+ ChannelStore.addChangeListener(this.onStoresChange);
+ UserStore.addChangeListener(this.onStoresChange);
}
- onUserChange() {
- this.setState({user: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
+ componentWillUnmount() {
+ PreferenceStore.removeChangeListener(this.onStoresChange);
+ ChannelStore.removeChangeListener(this.onStoresChange);
+ UserStore.removeChangeListener(this.onStoresChange);
}
render() {
- const channel = ChannelStore.getCurrent();
+ if (!this.validState()) {
+ return null;
+ }
+ const channel = this.state.channel;
var handleClick = null;
let postsContainer;
let createPost;
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 51be13dcf..882c575f0 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -57,20 +57,33 @@ export default class ChannelHeader extends React.Component {
memberChannel: ChannelStore.getCurrentMember(),
users: extraInfo.members,
userCount: extraInfo.member_count,
- searchVisible: SearchStore.getSearchResults() !== null
+ searchVisible: SearchStore.getSearchResults() !== null,
+ currentUser: UserStore.getCurrentUser()
};
}
+ validState() {
+ if (!this.state.channel ||
+ !this.state.memberChannel ||
+ !this.state.users ||
+ !this.state.userCount ||
+ !this.state.currentUser) {
+ return false;
+ }
+ return true;
+ }
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
SearchStore.addSearchChangeListener(this.onListenerChange);
PreferenceStore.addChangeListener(this.onListenerChange);
+ UserStore.addChangeListener(this.onListenerChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
SearchStore.removeSearchChangeListener(this.onListenerChange);
PreferenceStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeChangeListener(this.onListenerChange);
}
onListenerChange() {
const newState = this.getStateFromStores();
@@ -98,7 +111,7 @@ export default class ChannelHeader extends React.Component {
searchMentions(e) {
e.preventDefault();
- const user = this.props.user;
+ const user = this.state.currentUser;
let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
@@ -134,7 +147,7 @@ export default class ChannelHeader extends React.Component {
});
}
render() {
- if (this.state.channel === null) {
+ if (!this.validState()) {
return null;
}
@@ -163,8 +176,8 @@ export default class ChannelHeader extends React.Component {
</Popover>
);
let channelTitle = channel.display_name;
- const currentId = this.props.user.id;
- const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.props.user.roles);
+ const currentId = this.state.currentUser.id;
+ const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.state.currentUser.roles);
const isDirect = (this.state.channel.type === 'D');
if (isDirect) {
@@ -252,7 +265,7 @@ export default class ChannelHeader extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelInviteModal}
- dialogProps={{channel}}
+ dialogProps={{channel, currentUser: this.state.currentUser}}
>
<FormattedMessage
id='chanel_header.addMembers'
@@ -331,7 +344,11 @@ export default class ChannelHeader extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelNotificationsModal}
- dialogProps={{channel}}
+ dialogProps={{
+ channel,
+ channelMember: this.state.memberChannel,
+ currentUser: this.state.currentUser
+ }}
>
<FormattedMessage
id='channel_header.notificationPreferences'
@@ -497,5 +514,4 @@ export default class ChannelHeader extends React.Component {
}
ChannelHeader.propTypes = {
- user: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 6c8d51abb..4157812a9 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -4,8 +4,8 @@
import FilteredUserList from './filtered_user_list.jsx';
import LoadingScreen from './loading_screen.jsx';
-import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
+import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
@@ -16,18 +16,15 @@ import {FormattedMessage} from 'mm-intl';
const Modal = ReactBootstrap.Modal;
export default class ChannelInviteModal extends React.Component {
- constructor() {
- super();
+ constructor(props) {
+ super(props);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleInvite = this.handleInvite.bind(this);
-
+ this.getStateFromStores = this.getStateFromStores.bind(this);
this.createInviteButton = this.createInviteButton.bind(this);
- // the state gets populated when the modal is shown
- this.state = {
- loading: true
- };
+ this.state = this.getStateFromStores();
}
shouldComponentUpdate(nextProps, nextState) {
if (!this.props.show && !nextProps.show) {
@@ -63,6 +60,20 @@ export default class ChannelInviteModal extends React.Component {
};
}
+ const currentUser = UserStore.getCurrentUser();
+ if (!currentUser) {
+ return {
+ loading: true
+ };
+ }
+
+ const currentMember = ChannelStore.getCurrentMember();
+ if (!currentMember) {
+ return {
+ loading: true
+ };
+ }
+
const memberIds = extraInfo.members.map((user) => user.id);
var nonmembers = [];
@@ -78,7 +89,9 @@ export default class ChannelInviteModal extends React.Component {
return {
nonmembers,
- loading: false
+ loading: false,
+ currentUser,
+ currentMember
};
}
componentWillReceiveProps(nextProps) {
@@ -93,6 +106,11 @@ export default class ChannelInviteModal extends React.Component {
UserStore.removeChangeListener(this.onListenerChange);
}
}
+ componentWillUnmount() {
+ ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeChangeListener(this.onListenerChange);
+ }
onListenerChange() {
var newState = this.getStateFromStores();
if (!Utils.areObjectsEqual(this.state, newState)) {
@@ -144,7 +162,6 @@ export default class ChannelInviteModal extends React.Component {
if (Utils.windowHeight() <= 1200) {
maxHeight = Utils.windowHeight() - 300;
}
-
content = (
<FilteredUserList
style={{maxHeight}}
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
deleted file mode 100644
index e47f2aa50..000000000
--- a/web/react/components/channel_loader.jsx
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-/* This is a special React control with the sole purpose of making all the AsyncClient calls
- to the server on page load. This is to prevent other React controls from spamming
- AsyncClient with requests. */
-
-import * as AsyncClient from '../utils/async_client.jsx';
-import * as Client from '../utils/client.jsx';
-import SocketStore from '../stores/socket_store.jsx';
-import ChannelStore from '../stores/channel_store.jsx';
-import PostStore from '../stores/post_store.jsx';
-import UserStore from '../stores/user_store.jsx';
-import PreferenceStore from '../stores/preference_store.jsx';
-
-import * as Utils from '../utils/utils.jsx';
-import Constants from '../utils/constants.jsx';
-
-import {intlShape, injectIntl, defineMessages} from 'mm-intl';
-
-const holders = defineMessages({
- socketError: {
- id: 'channel_loader.socketError',
- defaultMessage: 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.'
- },
- someone: {
- id: 'channel_loader.someone',
- defaultMessage: 'Someone'
- },
- posted: {
- id: 'channel_loader.posted',
- defaultMessage: 'Posted'
- },
- uploadedImage: {
- id: 'channel_loader.uploadedImage',
- defaultMessage: ' uploaded an image'
- },
- uploadedFile: {
- id: 'channel_loader.uploadedFile',
- defaultMessage: ' uploaded a file'
- },
- something: {
- id: 'channel_loader.something',
- defaultMessage: ' did something new'
- },
- wrote: {
- id: 'channel_loader.wrote',
- defaultMessage: ' wrote: '
- },
- connectionError: {
- id: 'channel_loader.connection_error',
- defaultMessage: 'There appears to be a problem with your internet connection.'
- },
- unknownError: {
- id: 'channel_loader.unknown_error',
- defaultMessage: 'We received an unexpected status code from the server.'
- }
-});
-
-class ChannelLoader extends React.Component {
- constructor(props) {
- super(props);
-
- this.intervalId = null;
-
- this.onSocketChange = this.onSocketChange.bind(this);
-
- const {formatMessage} = this.props.intl;
- SocketStore.setTranslations({
- socketError: formatMessage(holders.socketError),
- someone: formatMessage(holders.someone),
- posted: formatMessage(holders.posted),
- uploadedImage: formatMessage(holders.uploadedImage),
- uploadedFile: formatMessage(holders.uploadedFile),
- something: formatMessage(holders.something),
- wrote: formatMessage(holders.wrote)
- });
-
- Client.setTranslations({
- connectionError: formatMessage(holders.connectionError),
- unknownError: formatMessage(holders.unknownError)
- });
-
- this.state = {};
- }
- componentDidMount() {
- /* Initial aysnc loads */
- AsyncClient.getPosts(ChannelStore.getCurrentId());
- AsyncClient.getChannels();
- AsyncClient.getChannelExtraInfo();
- AsyncClient.findTeams();
- AsyncClient.getMyTeam();
- setTimeout(() => AsyncClient.getStatuses(), 3000); // temporary until statuses are reworked a bit
-
- /* Perform pending post clean-up */
- PostStore.clearPendingPosts();
-
- /* Set up interval functions */
- this.intervalId = setInterval(() => AsyncClient.getStatuses(), 30000);
-
- /* Device tracking setup */
- var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
- if (iOS) {
- $('body').addClass('ios');
- }
-
- /* Set up tracking for whether the window is active */
- window.isActive = true;
-
- $(window).on('focus', function windowFocus() {
- AsyncClient.updateLastViewedAt();
- ChannelStore.resetCounts(ChannelStore.getCurrentId());
- ChannelStore.emitChange();
- window.isActive = true;
- });
-
- $(window).on('blur', function windowBlur() {
- window.isActive = false;
- });
-
- /* Start global change listeners setup */
- SocketStore.addChangeListener(this.onSocketChange);
-
- /* Update CSS classes to match user theme */
- var user = UserStore.getCurrentUser();
-
- if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
- Utils.applyTheme(user.theme_props);
- } else {
- Utils.applyTheme(Constants.THEMES.default);
- }
-
- // if preferences have already been stored in local storage do not wait until preference store change is fired and handled in channel.jsx
- const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
- Utils.applyFont(selectedFont);
-
- $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
- $(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
- } else {
- $(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
- $(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
- }
- });
-
- /* Prevent backspace from navigating back a page */
- $(window).on('keydown.preventBackspace', (e) => {
- if (e.which === 8 && !$(e.target).is('input, textarea')) {
- e.preventDefault();
- }
- });
- }
- componentWillUnmount() {
- clearInterval(this.intervalId);
-
- $(window).off('focus');
- $(window).off('blur');
-
- SocketStore.removeChangeListener(this.onSocketChange);
-
- $('body').off('click.userpopover');
- $('body').off('mouseenter mouseleave', '.post');
- $('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
-
- $('.modal').off('show.bs.modal');
-
- $(window).off('keydown.preventBackspace');
- }
- onSocketChange(msg) {
- if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
- UserStore.setStatus(msg.user_id, 'online');
- }
- }
- render() {
- return <div/>;
- }
-}
-
-ChannelLoader.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(ChannelLoader);
diff --git a/web/react/components/channel_notifications_modal.jsx b/web/react/components/channel_notifications_modal.jsx
index 7048434f8..acefaf024 100644
--- a/web/react/components/channel_notifications_modal.jsx
+++ b/web/react/components/channel_notifications_modal.jsx
@@ -6,7 +6,6 @@ import SettingItemMin from './setting_item_min.jsx';
import SettingItemMax from './setting_item_max.jsx';
import * as Client from '../utils/client.jsx';
-import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -15,7 +14,6 @@ export default class ChannelNotificationsModal extends React.Component {
constructor(props) {
super(props);
- this.onListenerChange = this.onListenerChange.bind(this);
this.updateSection = this.updateSection.bind(this);
this.handleSubmitNotifyLevel = this.handleSubmitNotifyLevel.bind(this);
@@ -26,58 +24,41 @@ export default class ChannelNotificationsModal extends React.Component {
this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
- const member = ChannelStore.getMember(props.channel.id);
this.state = {
- notifyLevel: member.notify_props.desktop,
- markUnreadLevel: member.notify_props.mark_unread,
- channelId: ChannelStore.getCurrentId(),
- activeSection: ''
+ activeSection: '',
+ notifyLevel: '',
+ unreadLevel: ''
};
}
+ updateSection(section) {
+ this.setState({activeSection: section});
+ }
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
- this.onListenerChange();
- ChannelStore.addChangeListener(this.onListenerChange);
- } else {
- ChannelStore.removeChangeListener(this.onListenerChange);
+ this.setState({
+ notifyLevel: nextProps.channelMember.notify_props.desktop,
+ unreadLevel: nextProps.channelMember.notify_props.mark_unread
+ });
}
}
- onListenerChange() {
- const curChannelId = ChannelStore.getCurrentId();
-
- if (!curChannelId) {
- return;
- }
-
- const newState = {channelId: curChannelId};
- const member = ChannelStore.getMember(curChannelId);
-
- if (member.notify_props.desktop !== this.state.notifyLevel || member.notify_props.mark_unread !== this.state.mark_unread) {
- newState.notifyLevel = member.notify_props.desktop;
- newState.markUnreadLevel = member.notify_props.mark_unread;
- }
-
- this.setState(newState);
- }
- updateSection(section) {
- this.setState({activeSection: section});
- }
handleSubmitNotifyLevel() {
- var channelId = this.state.channelId;
+ var channelId = this.props.channel.id;
var notifyLevel = this.state.notifyLevel;
- if (ChannelStore.getMember(channelId).notify_props.desktop === notifyLevel) {
+ if (this.props.channelMember.notify_props.desktop === notifyLevel) {
this.updateSection('');
return;
}
var data = {};
data.channel_id = channelId;
- data.user_id = UserStore.getCurrentId();
+ data.user_id = this.props.currentUser.id;
data.desktop = notifyLevel;
+ //TODO: This should be moved to event_helpers
Client.updateNotifyProps(data,
() => {
+ // YUCK
var member = ChannelStore.getMember(channelId);
member.notify_props.desktop = notifyLevel;
ChannelStore.setChannelMember(member);
@@ -92,11 +73,8 @@ export default class ChannelNotificationsModal extends React.Component {
this.setState({notifyLevel});
}
createNotifyLevelSection(serverError) {
- var handleUpdateSection;
-
- const user = UserStore.getCurrentUser();
- const globalNotifyLevel = user.notify_props.desktop;
-
+ // Get glabal user setting for notifications
+ const globalNotifyLevel = this.props.currentUser.notify_props.desktop;
let globalNotifyLevelName;
if (globalNotifyLevel === 'all') {
globalNotifyLevelName = (
@@ -128,13 +106,15 @@ export default class ChannelNotificationsModal extends React.Component {
/>
);
+ const notificationLevel = this.state.notifyLevel;
+
if (this.state.activeSection === 'desktop') {
- var notifyActive = [false, false, false, false];
- if (this.state.notifyLevel === 'default') {
+ const notifyActive = [false, false, false, false];
+ if (notificationLevel === 'default') {
notifyActive[0] = true;
- } else if (this.state.notifyLevel === 'all') {
+ } else if (notificationLevel === 'all') {
notifyActive[1] = true;
- } else if (this.state.notifyLevel === 'mention') {
+ } else if (notificationLevel === 'mention') {
notifyActive[2] = true;
} else {
notifyActive[3] = true;
@@ -196,7 +176,7 @@ export default class ChannelNotificationsModal extends React.Component {
</div>
);
- handleUpdateSection = function updateSection(e) {
+ const handleUpdateSection = function updateSection(e) {
this.updateSection('');
this.onListenerChange();
e.preventDefault();
@@ -224,7 +204,7 @@ export default class ChannelNotificationsModal extends React.Component {
}
var describe;
- if (this.state.notifyLevel === 'default') {
+ if (notificationLevel === 'default') {
describe = (
<FormattedMessage
id='channel_notifications.globalDefault'
@@ -233,45 +213,44 @@ export default class ChannelNotificationsModal extends React.Component {
}}
/>
);
- } else if (this.state.notifyLevel === 'mention') {
+ } else if (notificationLevel === 'mention') {
describe = (<FormattedMessage id='channel_notifications.onlyMentions'/>);
- } else if (this.state.notifyLevel === 'all') {
+ } else if (notificationLevel === 'all') {
describe = (<FormattedMessage id='channel_notifications.allActivity'/>);
} else {
describe = (<FormattedMessage id='channel_notifications.never'/>);
}
- handleUpdateSection = function updateSection(e) {
- this.updateSection('desktop');
- e.preventDefault();
- }.bind(this);
-
return (
<SettingItemMin
title={sendDesktop}
describe={describe}
- updateSection={handleUpdateSection}
+ updateSection={() => {
+ this.updateSection('desktop');
+ }}
/>
);
}
handleSubmitMarkUnreadLevel() {
- const channelId = this.state.channelId;
- const markUnreadLevel = this.state.markUnreadLevel;
+ const channelId = this.props.channel.id;
+ const markUnreadLevel = this.state.unreadLevel;
- if (ChannelStore.getMember(channelId).notify_props.mark_unread === markUnreadLevel) {
+ if (this.props.channelMember.notify_props.mark_unread === markUnreadLevel) {
this.updateSection('');
return;
}
const data = {
channel_id: channelId,
- user_id: UserStore.getCurrentId(),
+ user_id: this.props.currentUser.id,
mark_unread: markUnreadLevel
};
+ //TODO: This should be fixed, moved to event_helpers
Client.updateNotifyProps(data,
() => {
+ // Yuck...
var member = ChannelStore.getMember(channelId);
member.notify_props.mark_unread = markUnreadLevel;
ChannelStore.setChannelMember(member);
@@ -283,8 +262,8 @@ export default class ChannelNotificationsModal extends React.Component {
);
}
- handleUpdateMarkUnreadLevel(markUnreadLevel) {
- this.setState({markUnreadLevel});
+ handleUpdateMarkUnreadLevel(unreadLevel) {
+ this.setState({unreadLevel});
}
createMarkUnreadLevelSection(serverError) {
@@ -303,7 +282,7 @@ export default class ChannelNotificationsModal extends React.Component {
<label>
<input
type='radio'
- checked={this.state.markUnreadLevel === 'all'}
+ checked={this.state.unreadLevel === 'all'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'all')}
/>
<FormattedMessage
@@ -317,7 +296,7 @@ export default class ChannelNotificationsModal extends React.Component {
<label>
<input
type='radio'
- checked={this.state.markUnreadLevel === 'mention'}
+ checked={this.state.unreadLevel === 'mention'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'mention')}
/>
<FormattedMessage id='channel_notifications.onlyMentions'/>
@@ -355,7 +334,7 @@ export default class ChannelNotificationsModal extends React.Component {
} else {
let describe;
- if (!this.state.markUnreadLevel || this.state.markUnreadLevel === 'all') {
+ if (!this.state.unreadLevel || this.state.unreadLevel === 'all') {
describe = (
<FormattedMessage
id='channel_notifications.allUnread'
@@ -430,5 +409,7 @@ export default class ChannelNotificationsModal extends React.Component {
ChannelNotificationsModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
- channel: React.PropTypes.object.isRequired
+ channel: React.PropTypes.object.isRequired,
+ channelMember: React.PropTypes.object.isRequired,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/channel_view.jsx b/web/react/components/channel_view.jsx
index 9c4131292..76744d6d7 100644
--- a/web/react/components/channel_view.jsx
+++ b/web/react/components/channel_view.jsx
@@ -2,34 +2,11 @@
// See License.txt for license information.
import CenterPanel from '../components/center_panel.jsx';
-import Sidebar from '../components/sidebar.jsx';
-import SidebarRight from '../components/sidebar_right.jsx';
-import SidebarRightMenu from '../components/sidebar_right_menu.jsx';
export default class ChannelView extends React.Component {
render() {
return (
- <div className='container-fluid'>
- <div
- className='sidebar--right'
- id='sidebar-right'
- >
- <SidebarRight/>
- </div>
- <div
- className='sidebar--menu'
- id='sidebar-menu'
- >
- <SidebarRightMenu/>
- </div>
- <div
- className='sidebar--left'
- id='sidebar-left'
- >
- <Sidebar/>
- </div>
- <CenterPanel/>
- </div>
+ <CenterPanel/>
);
}
}
@@ -37,4 +14,5 @@ ChannelView.defaultProps = {
};
ChannelView.propTypes = {
+ params: React.PropTypes.object
};
diff --git a/web/react/components/claim/claim_account.jsx b/web/react/components/claim/claim_account.jsx
index 5b3b584ee..42fd8dafa 100644
--- a/web/react/components/claim/claim_account.jsx
+++ b/web/react/components/claim/claim_account.jsx
@@ -3,6 +3,7 @@
import EmailToSSO from './email_to_sso.jsx';
import SSOToEmail from './sso_to_email.jsx';
+import TeamStore from '../../stores/team_store.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -10,11 +11,46 @@ export default class ClaimAccount extends React.Component {
constructor(props) {
super(props);
+ this.onTeamChange = this.onTeamChange.bind(this);
+ this.updateStateFromStores = this.updateStateFromStores.bind(this);
+
this.state = {};
}
+ componentWillMount() {
+ this.setState({
+ email: this.props.location.query.email,
+ newType: this.props.location.query.new_type,
+ oldType: this.props.location.query.old_type,
+ teamName: this.props.params.team,
+ teamDisplayName: ''
+ });
+ this.updateStateFromStores();
+ }
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamChange);
+ }
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamChange);
+ }
+ updateStateFromStores() {
+ const team = TeamStore.getByName(this.state.teamName);
+ let displayName = '';
+ if (team) {
+ displayName = team.displayName;
+ }
+ this.setState({
+ teamDisplayName: displayName
+ });
+ }
+ onTeamChange() {
+ this.updateStateFromStores();
+ }
render() {
+ if (this.state.teamDisplayName === '') {
+ return (<div/>);
+ }
let content;
- if (this.props.email === '') {
+ if (this.state.email === '') {
content = (
<p>
<FormattedMessage
@@ -23,36 +59,55 @@ export default class ClaimAccount extends React.Component {
/>
</p>
);
- } else if (this.props.currentType === '' && this.props.newType !== '') {
+ } else if (this.state.oldType === '' && this.state.newType !== '') {
content = (
<EmailToSSO
- email={this.props.email}
- type={this.props.newType}
- teamName={this.props.teamName}
- teamDisplayName={this.props.teamDisplayName}
+ email={this.state.email}
+ type={this.state.newType}
+ teamName={this.state.teamName}
+ teamDisplayName={this.state.teamDisplayName}
/>
);
} else {
content = (
<SSOToEmail
- email={this.props.email}
- currentType={this.props.currentType}
- teamName={this.props.teamName}
- teamDisplayName={this.props.teamDisplayName}
+ email={this.state.email}
+ currentType={this.state.oldType}
+ teamName={this.state.teamName}
+ teamDisplayName={this.state.teamDisplayName}
/>
);
}
- return content;
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <div id='claim'>
+ {content}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
}
}
ClaimAccount.defaultProps = {
};
ClaimAccount.propTypes = {
- currentType: React.PropTypes.string.isRequired,
- newType: React.PropTypes.string.isRequired,
- email: React.PropTypes.string.isRequired,
- teamName: React.PropTypes.string.isRequired,
- teamDisplayName: React.PropTypes.string.isRequired
+ params: React.PropTypes.object.isRequired,
+ location: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/claim/sso_to_email.jsx b/web/react/components/claim/sso_to_email.jsx
index 74137082a..a16efb57b 100644
--- a/web/react/components/claim/sso_to_email.jsx
+++ b/web/react/components/claim/sso_to_email.jsx
@@ -159,7 +159,7 @@ SSOToEmail.propTypes = {
currentType: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
- teamDisplayName: React.PropTypes.string.isRequired
+ teamDisplayName: React.PropTypes.string
};
export default injectIntl(SSOToEmail);
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 62319b1a7..69cc74842 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -9,7 +9,7 @@ import PostDeletedModal from './post_deleted_modal.jsx';
import TutorialTip from './tutorial/tutorial_tip.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
import * as Utils from '../utils/utils.jsx';
@@ -165,7 +165,7 @@ class CreatePost extends React.Component {
const channel = ChannelStore.get(this.state.channelId);
- EventHelpers.emitUserPostedEvent(post);
+ GlobalActions.emitUserPostedEvent(post);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
Client.createPost(post, channel,
@@ -177,7 +177,7 @@ class CreatePost extends React.Component {
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
- EventHelpers.emitPostRecievedEvent(data);
+ GlobalActions.emitPostRecievedEvent(data);
},
(err) => {
if (err.id === 'api.post.create_post.root_id.app_error') {
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index d9113bc9f..70e7a67a8 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -9,6 +9,8 @@ import Constants from '../utils/constants.jsx';
import {FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
+
export default class DeleteChannelModal extends React.Component {
constructor(props) {
super(props);
@@ -21,11 +23,11 @@ export default class DeleteChannelModal extends React.Component {
return;
}
+ browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
Client.deleteChannel(
this.props.channel.id,
() => {
AsyncClient.getChannels(true);
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
},
(err) => {
AsyncClient.dispatchError(err, 'handleDelete');
diff --git a/web/react/components/do_verify_email.jsx b/web/react/components/do_verify_email.jsx
new file mode 100644
index 000000000..df98bf463
--- /dev/null
+++ b/web/react/components/do_verify_email.jsx
@@ -0,0 +1,82 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+import * as Client from '../utils/client.jsx';
+import LoadingScreen from './loading_screen.jsx';
+
+import {browserHistory} from 'react-router';
+
+export default class DoVerifyEmail extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ verifyStatus: 'pending',
+ serverError: ''
+ };
+ }
+ componentWillMount() {
+ const uid = this.props.location.query.uid;
+ const hid = this.props.location.query.hid;
+ const teamName = this.props.location.query.teamname;
+ const email = this.props.location.query.email;
+
+ Client.verifyEmail(
+ () => {
+ browserHistory.push('/' + teamName + '/login?extra=verified&email=' + email);
+ },
+ (err) => {
+ this.setState({verifyStatus: 'failure', serverError: err.message});
+ },
+ uid,
+ hid
+ );
+ }
+ render() {
+ if (this.state.verifyStatus !== 'failure') {
+ return (<LoadingScreen/>);
+ }
+
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='email_verify.almost'
+ defaultMessage='{siteName}: You are almost done'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h3>
+ <div>
+ <p>
+ <FormattedMessage id='email_verify.verifyFailed'/>
+ </p>
+ <p className='alert alert-danger'>
+ <i className='fa fa-times'/>
+ {this.state.serverError}
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+DoVerifyEmail.defaultProps = {
+};
+DoVerifyEmail.propTypes = {
+ location: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/docs.jsx b/web/react/components/docs.jsx
deleted file mode 100644
index 6d3a109c2..000000000
--- a/web/react/components/docs.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as TextFormatting from '../utils/text_formatting.jsx';
-import UserStore from '../stores/user_store.jsx';
-
-export default class Docs extends React.Component {
- constructor(props) {
- super(props);
- UserStore.setCurrentUser(global.window.mm_user || {});
-
- this.state = {text: ''};
- const errorState = {text: '## 404'};
-
- if (props.site) {
- $.get(`/static/help/${props.site}_${global.window.mm_locale}.md`).then((response) => {
- this.setState({text: response});
- }, () => {
- this.setState(errorState);
- });
- } else {
- this.setState(errorState);
- }
- }
-
- render() {
- return (
- <div
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.text)}}
- >
- </div>
- );
- }
-}
-
-Docs.defaultProps = {
- site: ''
-};
-Docs.propTypes = {
- site: React.PropTypes.string
-};
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index 380ca7bde..f02239fcf 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -3,7 +3,7 @@
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Textbox from './textbox.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import PostStore from '../stores/post_store.jsx';
@@ -45,7 +45,7 @@ class EditPostModal extends React.Component {
delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', tempState);
$('#edit_post').modal('hide');
- EventHelpers.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments);
+ GlobalActions.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments);
return;
}
diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx
deleted file mode 100644
index 702a20eba..000000000
--- a/web/react/components/email_verify.jsx
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
-
-export default class EmailVerify extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleResend = this.handleResend.bind(this);
-
- this.state = {};
- }
- handleResend() {
- const newAddress = window.location.href.replace('&resend_success=true', '');
- window.location.href = newAddress + '&resend=true';
- }
- render() {
- var title = '';
- var body = '';
- var resend = '';
- var resendConfirm = '';
- if (this.props.isVerified === 'true') {
- title = (
- <FormattedMessage
- id='email_verify.verified'
- defaultMessage='{siteName} Email Verified'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- );
- body = (
- <FormattedHTMLMessage
- id='email_verify.verifiedBody'
- defaultMessage='<p>Your email has been verified! Click <a href={url}>here</a> to log in.</p>'
- values={{
- url: this.props.teamURL + '?email=' + this.props.userEmail
- }}
- />
- );
- } else {
- title = (
- <FormattedMessage
- id='email_verify.almost'
- defaultMessage='{siteName}: You are almost done'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- );
- body = (
- <p>
- <FormattedMessage
- id='email_verify.notVerifiedBody'
- defaultMessage='Please verify your email address. Check your inbox for an email.'
- />
- </p>
- );
- resend = (
- <button
- onClick={this.handleResend}
- className='btn btn-primary'
- >
- <FormattedMessage
- id='email_verify.resend'
- defaultMessage='Resend Email'
- />
- </button>
- );
- if (this.props.resendSuccess) {
- resendConfirm = (
- <div><br/><p className='alert alert-success'><i className='fa fa-check'></i>
- <FormattedMessage
- id='email_verify.sent'
- defaultMessage=' Verification email sent.'
- />
- </p></div>);
- }
- }
-
- return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>{title}</h3>
- <div>
- {body}
- {resend}
- {resendConfirm}
- </div>
- </div>
- </div>
- );
- }
-}
-
-EmailVerify.defaultProps = {
- isVerified: 'false',
- teamURL: '',
- userEmail: '',
- resendSuccess: 'false'
-};
-EmailVerify.propTypes = {
- isVerified: React.PropTypes.string,
- teamURL: React.PropTypes.string,
- userEmail: React.PropTypes.string,
- resendSuccess: React.PropTypes.string
-};
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index c719c6c7d..8abcac8c3 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -43,7 +43,7 @@ class FileAttachment extends React.Component {
if (type === 'image') {
var self = this; // Need this reference since we use the given "this"
- $('<img/>').attr('src', fileInfo.path + '_thumb.jpg?' + utils.getSessionIndex()).load(function loadWrapper(path, name) {
+ $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) {
return function loader() {
$(this).remove();
if (name in self.refs) {
@@ -114,7 +114,7 @@ class FileAttachment extends React.Component {
var re3 = new RegExp('\\)', 'g');
var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
- $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg?' + utils.getSessionIndex() + ')');
+ $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
}
}
removeBackgroundImage(name) {
@@ -185,6 +185,7 @@ class FileAttachment extends React.Component {
data-toggle='tooltip'
title={this.props.intl.formatMessage(holders.download) + ' \"' + filenameString + '\"'}
className='post-image__name'
+ target='_blank'
>
{trimmedFilename}
</a>
@@ -193,6 +194,7 @@ class FileAttachment extends React.Component {
href={fileUrl}
download={filenameString}
className='post-image__download'
+ target='_blank'
>
<span
className='fa fa-download'
diff --git a/web/react/components/find_team.jsx b/web/react/components/find_team.jsx
deleted file mode 100644
index 3ff9787ad..000000000
--- a/web/react/components/find_team.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
-
-var holders = defineMessages({
- submitError: {
- id: 'find_team.submitError',
- defaultMessage: 'Please enter a valid email address'
- },
- placeholder: {
- id: 'find_team.placeholder',
- defaultMessage: 'you@domain.com'
- }
-});
-
-class FindTeam extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
-
- this.handleSubmit = this.handleSubmit.bind(this);
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- var state = { };
-
- var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
- if (!email || !utils.isEmail(email)) {
- state.email_error = this.props.intl.formatMessage(holders.submitError);
- this.setState(state);
- return;
- }
-
- state.email_error = '';
-
- client.findTeamsSendEmail(email,
- function success() {
- state.sent = true;
- this.setState(state);
- }.bind(this),
- function fail(err) {
- state.email_error = err.message;
- this.setState(state);
- }.bind(this)
- );
- }
-
- render() {
- var emailError = null;
- var emailErrorClass = 'form-group';
-
- if (this.state.email_error) {
- emailError = <label className='control-label'>{this.state.email_error}</label>;
- emailErrorClass = 'form-group has-error';
- }
-
- if (this.state.sent) {
- return (
- <div>
- <h4>
- <FormattedMessage
- id='find_team.findTitle'
- defaultMessage='Find Your Team'
- />
- </h4>
- <p>
- <FormattedMessage
- id='find_team.findDescription'
- defaultMessage='An email was sent with links to any teams to which you are a member.'
- />
- </p>
- </div>
- );
- }
-
- return (
- <div>
- <h4>
- <FormattedMessage
- id='find_team.findTitle'
- defaultMessage='Find Your Team'
- />
- </h4>
- <form onSubmit={this.handleSubmit}>
- <p>
- <FormattedMessage
- id='find_team.getLinks'
- defaultMessage='Get an email with links to any teams to which you are a member.'
- />
- </p>
- <div className='form-group'>
- <label className='control-label'>
- <FormattedMessage
- id='find_team.email'
- defaultMessage='Email'
- />
- </label>
- <div className={emailErrorClass}>
- <input
- type='text'
- ref='email'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.placeholder)}
- maxLength='128'
- spellCheck='false'
- />
- {emailError}
- </div>
- </div>
- <button
- className='btn btn-md btn-primary'
- type='submit'
- >
- <FormattedMessage
- id='find_team.send'
- defaultMessage='Send'
- />
- </button>
- </form>
- </div>
- );
- }
-}
-
-FindTeam.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(FindTeam); \ No newline at end of file
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 184ba1357..71cd5b8b6 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -5,7 +5,7 @@ import * as utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
import * as Client from '../utils/client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import ModalStore from '../stores/modal_store.jsx';
import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -223,7 +223,7 @@ class InviteMemberModal extends React.Component {
showGetTeamInviteLinkModal() {
this.handleHide(false);
- EventHelpers.showGetTeamInviteLinkModal();
+ GlobalActions.showGetTeamInviteLinkModal();
}
render() {
diff --git a/web/react/components/logged_in.jsx b/web/react/components/logged_in.jsx
new file mode 100644
index 000000000..1ed3694e9
--- /dev/null
+++ b/web/react/components/logged_in.jsx
@@ -0,0 +1,224 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as AsyncClient from '../utils/async_client.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import UserStore from '../stores/user_store.jsx';
+import SocketStore from '../stores/socket_store.jsx';
+import ChannelStore from '../stores/channel_store.jsx';
+import PreferenceStore from '../stores/preference_store.jsx';
+import * as Utils from '../utils/utils.jsx';
+import Constants from '../utils/constants.jsx';
+import ErrorBar from '../components/error_bar.jsx';
+
+import {browserHistory} from 'react-router';
+
+import SidebarRight from '../components/sidebar_right.jsx';
+import SidebarRightMenu from '../components/sidebar_right_menu.jsx';
+
+// Modals
+import GetPostLinkModal from '../components/get_post_link_modal.jsx';
+import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx';
+import EditPostModal from '../components/edit_post_modal.jsx';
+import DeletePostModal from '../components/delete_post_modal.jsx';
+import MoreChannelsModal from '../components/more_channels.jsx';
+import TeamSettingsModal from '../components/team_settings_modal.jsx';
+import RemovedFromChannelModal from '../components/removed_from_channel_modal.jsx';
+import RegisterAppModal from '../components/register_app_modal.jsx';
+import ImportThemeModal from '../components/user_settings/import_theme_modal.jsx';
+import InviteMemberModal from '../components/invite_member_modal.jsx';
+import SelectTeamModal from '../components/admin_console/select_team_modal.jsx';
+
+const CLIENT_STATUS_INTERVAL = 30000;
+const BACKSPACE_CHAR = 8;
+
+export default class LoggedIn extends React.Component {
+ constructor(params) {
+ super(params);
+
+ this.onUserChanged = this.onUserChanged.bind(this);
+ }
+ onUserChanged() {
+ // Grab the current user
+ const user = UserStore.getCurrentUser();
+
+ // Update segment indentify
+ if (global.window.mm_config.SegmentDeveloperKey != null && global.window.mm_config.SegmentDeveloperKey !== '') {
+ global.window.analytics.identify(user.id, {
+ name: user.nickname,
+ email: user.email,
+ createdAt: user.create_at,
+ username: user.username,
+ team_id: user.team_id,
+ id: user.id
+ });
+ }
+
+ // Update CSS classes to match user theme
+ if (user) {
+ if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
+ Utils.applyTheme(user.theme_props);
+ } else {
+ Utils.applyTheme(Constants.THEMES.default);
+ }
+ }
+ }
+ onSocketChange(msg) {
+ if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
+ UserStore.setStatus(msg.user_id, 'online');
+ }
+ }
+ componentWillMount() {
+ // Emit view action
+ GlobalActions.viewLoggedIn();
+
+ // Listen for user
+ UserStore.addChangeListener(this.onUserChanged);
+
+ // Add listner for socker store
+ SocketStore.addChangeListener(this.onSocketChange);
+
+ // Get all statuses regularally. (Soon to be switched to websocket)
+ this.intervalId = setInterval(() => AsyncClient.getStatuses(), CLIENT_STATUS_INTERVAL);
+
+ // Force logout of all tabs if one tab is logged out
+ $(window).bind('storage', (e) => {
+ // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
+ if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
+ // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
+ if (window.BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
+ return;
+ }
+
+ console.log('detected logout from a different tab'); //eslint-disable-line no-console
+ browserHistory.push('/' + this.props.params.team);
+ }
+
+ if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
+ // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
+ if (window.BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
+ return;
+ }
+
+ console.log('detected login from a different tab'); //eslint-disable-line no-console
+ location.reload();
+ }
+ });
+
+ // Because current CSS requires the root tag to have specific stuff
+ $('#root').attr('class', 'channel-view');
+
+ // ???
+ $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
+ $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
+ } else {
+ $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
+ $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
+ }
+ });
+
+ $('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
+ $(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
+ } else {
+ $(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
+ $(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
+ }
+ });
+
+ $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
+ $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
+ } else {
+ $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
+ $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
+ }
+ });
+
+ // Device tracking setup
+ var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
+ if (iOS) {
+ $('body').addClass('ios');
+ }
+
+ // Set up tracking for whether the window is active
+ window.isActive = true;
+ $(window).on('focus', () => {
+ AsyncClient.updateLastViewedAt();
+ ChannelStore.resetCounts(ChannelStore.getCurrentId());
+ ChannelStore.emitChange();
+ window.isActive = true;
+ });
+ $(window).on('blur', () => {
+ window.isActive = false;
+ });
+
+ // if preferences have already been stored in local storage do not wait until preference store change is fired and handled in channel.jsx
+ const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
+ Utils.applyFont(selectedFont);
+
+ // Pervent backspace from navigating back a page
+ $(window).on('keydown.preventBackspace', (e) => {
+ if (e.which === BACKSPACE_CHAR && !$(e.target).is('input, textarea')) {
+ e.preventDefault();
+ }
+ });
+ }
+ componentWillUnmount() {
+ $('#root').attr('class', '');
+ clearInterval(this.intervalId);
+
+ $(window).off('focus');
+ $(window).off('blur');
+
+ SocketStore.removeChangeListener(this.onSocketChange);
+ UserStore.removeChangeListener(this.onUserChanged);
+
+ $('body').off('click.userpopover');
+ $('body').off('mouseenter mouseleave', '.post');
+ $('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
+
+ $('.modal').off('show.bs.modal');
+
+ $(window).off('keydown.preventBackspace');
+ }
+ render() {
+ return (
+ <div className='channel-view'>
+ <ErrorBar/>
+ <div className='container-fluid'>
+ <SidebarRight/>
+ <SidebarRightMenu/>
+ {this.props.sidebar}
+ {this.props.center}
+
+ <GetPostLinkModal/>
+ <GetTeamInviteLinkModal/>
+ <InviteMemberModal/>
+ <ImportThemeModal/>
+ <TeamSettingsModal/>
+ <MoreChannelsModal/>
+ <EditPostModal/>
+ <DeletePostModal/>
+ <RemovedFromChannelModal/>
+ <RegisterAppModal/>
+ <SelectTeamModal/>
+ </div>
+ </div>
+ );
+ }
+}
+
+LoggedIn.defaultProps = {
+};
+
+LoggedIn.propTypes = {
+ children: React.PropTypes.object,
+ sidebar: React.PropTypes.object,
+ center: React.PropTypes.object,
+ params: React.PropTypes.object
+};
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 581b8e0b5..d3ee35082 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -6,82 +6,120 @@ import LoginUsername from './login_username.jsx';
import LoginLdap from './login_ldap.jsx';
import * as Utils from '../utils/utils.jsx';
+import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
+import TeamStore from '../stores/team_store.jsx';
import {FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
export default class Login extends React.Component {
constructor(props) {
super(props);
- this.state = {};
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onTeamChange = this.onTeamChange.bind(this);
+
+ this.state = this.getStateFromStores();
+ }
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamChange);
+ Client.getMeLoggedIn((data) => {
+ if (data && data.logged_in !== 'false') {
+ browserHistory.push('/' + this.props.params.team + '/channels/town-square');
+ }
+ });
+ }
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamChange);
+ }
+ getStateFromStores() {
+ return {
+ currentTeam: TeamStore.getByName(this.props.params.team)
+ };
+ }
+ onTeamChange() {
+ this.setState(this.getStateFromStores());
}
render() {
- const teamDisplayName = this.props.teamDisplayName;
- const teamName = this.props.teamName;
+ const currentTeam = this.state.currentTeam;
+ if (currentTeam == null) {
+ return <div/>;
+ }
+
+ const teamDisplayName = currentTeam.display_name;
+ const teamName = currentTeam.name;
+ const ldapEnabled = global.window.mm_config.EnableLdap === 'true';
+ const usernameSigninEnabled = global.window.mm_config.EnableSignInWithUsername === 'true';
let loginMessage = [];
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
loginMessage.push(
- <a
- className='btn btn-custom-login gitlab'
- key='gitlab'
- href={'/' + teamName + '/login/gitlab'}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.gitlab'
- defaultMessage='with GitLab'
- />
- </span>
- </a>
+ <a
+ className='btn btn-custom-login gitlab'
+ key='gitlab'
+ href={'/api/v1/oauth/gitlab/login?team=' + encodeURIComponent(teamName)}
+ >
+ <span className='icon'/>
+ <span>
+ <FormattedMessage
+ id='login.gitlab'
+ defaultMessage='with GitLab'
+ />
+ </span>
+ </a>
);
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
loginMessage.push(
- <a
- className='btn btn-custom-login google'
- key='google'
- href={'/' + teamName + '/login/google'}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.google'
- defaultMessage='with Google Apps'
- />
- </span>
- </a>
- );
+ <a
+ className='btn btn-custom-login google'
+ key='google'
+ href={'/api/v1/oauth/google/login?team=' + encodeURIComponent(teamName)}
+ >
+ <span className='icon'/>
+ <span>
+ <FormattedMessage
+ id='login.google'
+ defaultMessage='with Google Apps'
+ />
+ </span>
+ </a>
+ );
}
const extraParam = Utils.getUrlParameter('extra');
let extraBox = '';
if (extraParam) {
- let msg;
if (extraParam === Constants.SIGNIN_CHANGE) {
- msg = (
- <FormattedMessage
- id='login.changed'
- defaultMessage=' Sign-in method changed successfully'
- />
+ extraBox = (
+ <div className='alert alert-success'>
+ <i className='fa fa-check'/>
+ <FormattedMessage
+ id='login.changed'
+ defaultMessage=' Sign-in method changed successfully'
+ />
+ </div>
);
} else if (extraParam === Constants.SIGNIN_VERIFIED) {
- msg = (
- <FormattedMessage
- id='login.verified'
- defaultMessage=' Email Verified'
- />
- );
- }
-
- if (msg != null) {
extraBox = (
<div className='alert alert-success'>
<i className='fa fa-check'/>
- {msg}
+ <FormattedMessage
+ id='login.verified'
+ defaultMessage=' Email Verified'
+ />
+ </div>
+ );
+ } else if (extraParam === Constants.SESSION_EXPIRED) {
+ extraBox = (
+ <div className='alert alert-warning'>
+ <i className='fa fa-exclamation-triangle'/>
+ <FormattedMessage
+ id='login.session_expired'
+ defaultMessage=' Your session has expired. Please login again.'
+ />
</div>
);
}
@@ -91,7 +129,7 @@ export default class Login extends React.Component {
if (global.window.mm_config.EnableSignInWithEmail === 'true') {
emailSignup = (
<LoginEmail
- teamName={this.props.teamName}
+ teamName={teamName}
/>
);
}
@@ -125,7 +163,7 @@ export default class Login extends React.Component {
}
let userSignUp = null;
- if (this.props.inviteId) {
+ if (currentTeam.allow_open_invite) {
userSignUp = (
<div>
<span>
@@ -134,7 +172,7 @@ export default class Login extends React.Component {
defaultMessage="Don't have an account? "
/>
<a
- href={'/signup_user_complete/?id=' + this.props.inviteId}
+ href={'/signup_user_complete/?id=' + currentTeam.invite_id}
className='signup-team-login'
>
<FormattedMessage
@@ -168,22 +206,23 @@ export default class Login extends React.Component {
if (global.window.mm_config.EnableLdap === 'true') {
ldapLogin = (
<LoginLdap
- teamName={this.props.teamName}
+ teamName={teamName}
/>
);
}
- let findTeams = null;
- if (!Utils.isMobileApp()) {
- findTeams = (
- <div className='form-group margin--extra form-group--small'>
- <span>
- <a href='/find_team'>
- <FormattedMessage
- id='login.find'
- defaultMessage='Find your other teams'
- />
- </a></span>
+ if (ldapEnabled && (loginMessage.length > 0 || emailSignup || usernameSigninEnabled)) {
+ ldapLogin = (
+ <div>
+ <div className='or__container'>
+ <FormattedMessage
+ id='login.or'
+ defaultMessage='or'
+ />
+ </div>
+ <LoginLdap
+ teamName={teamName}
+ />
</div>
);
}
@@ -192,49 +231,72 @@ export default class Login extends React.Component {
if (global.window.mm_config.EnableSignInWithUsername === 'true') {
usernameLogin = (
<LoginUsername
- teamName={this.props.teamName}
+ teamName={teamName}
/>
);
}
- return (
- <div className='signup-team__container'>
- <h5 className='margin--less'>
- <FormattedMessage
- id='login.signTo'
- defaultMessage='Sign in to:'
- />
- </h5>
- <h2 className='signup-team__name'>{teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>
- <FormattedMessage
- id='login.on'
- defaultMessage='on {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
+ if (usernameSigninEnabled && (loginMessage.length > 0 || emailSignup || ldapEnabled)) {
+ usernameLogin = (
+ <div>
+ <div className='or__container'>
+ <FormattedMessage
+ id='login.or'
+ defaultMessage='or'
+ />
+ </div>
+ <LoginUsername
+ teamName={teamName}
/>
- </h2>
- {extraBox}
- {loginMessage}
- {emailSignup}
- {usernameLogin}
- {ldapLogin}
- {userSignUp}
- {findTeams}
- {forgotPassword}
- {teamSignUp}
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h5 className='margin--less'>
+ <FormattedMessage
+ id='login.signTo'
+ defaultMessage='Sign in to:'
+ />
+ </h5>
+ <h2 className='signup-team__name'>{teamDisplayName}</h2>
+ <h2 className='signup-team__subdomain'>
+ <FormattedMessage
+ id='login.on'
+ defaultMessage='on {siteName}'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h2>
+ {extraBox}
+ {loginMessage}
+ {emailSignup}
+ {usernameLogin}
+ {ldapLogin}
+ {userSignUp}
+ {forgotPassword}
+ {teamSignUp}
+ </div>
+ </div>
</div>
);
}
}
Login.defaultProps = {
- teamName: '',
- teamDisplayName: ''
};
Login.propTypes = {
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
- inviteId: React.PropTypes.string
+ params: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/login_email.jsx b/web/react/components/login_email.jsx
index cf1e1bc40..3e0d8919d 100644
--- a/web/react/components/login_email.jsx
+++ b/web/react/components/login_email.jsx
@@ -4,6 +4,7 @@
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import UserStore from '../stores/user_store.jsx';
+import {browserHistory} from 'react-router';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
@@ -72,13 +73,7 @@ class LoginEmail extends React.Component {
Client.loginByEmail(name, email, password,
() => {
UserStore.setLastEmail(email);
-
- const redirect = Utils.getUrlParameter('redirect');
- if (redirect) {
- window.location.href = decodeURIComponent(redirect);
- } else {
- window.location.href = '/' + name + '/channels/town-square';
- }
+ browserHistory.push('/' + name + '/channels/town-square');
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
@@ -167,4 +162,4 @@ LoginEmail.propTypes = {
teamName: React.PropTypes.string.isRequired
};
-export default injectIntl(LoginEmail); \ No newline at end of file
+export default injectIntl(LoginEmail);
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index 93fe6c05a..974f026d0 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -56,9 +56,13 @@ export default class Navbar extends React.Component {
return {
channel: ChannelStore.getCurrent(),
member: ChannelStore.getCurrentMember(),
- users: ChannelStore.getCurrentExtraInfo().members
+ users: ChannelStore.getCurrentExtraInfo().members,
+ currentUser: UserStore.getCurrentUser()
};
}
+ stateValid() {
+ return this.state.channel && this.state.member && this.state.users && this.state.currentUser;
+ }
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
ChannelStore.addExtraInfoChangeListener(this.onChange);
@@ -201,7 +205,7 @@ export default class Navbar extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelInviteModal}
- dialogProps={{channel}}
+ dialogProps={{channel, currentUser: this.state.currentUser}}
>
<FormattedMessage
id='navbar.addMembers'
@@ -286,7 +290,11 @@ export default class Navbar extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelNotificationsModal}
- dialogProps={{channel}}
+ dialogProps={{
+ channel,
+ channelMember: this.state.member,
+ currentUser: this.state.currentUser
+ }}
>
<FormattedMessage
id='navbar.preferences'
@@ -412,7 +420,11 @@ export default class Navbar extends React.Component {
return buttons;
}
render() {
- var currentId = UserStore.getCurrentId();
+ if (!this.stateValid()) {
+ return null;
+ }
+
+ var currentId = this.state.currentUser.id;
var channel = this.state.channel;
var channelTitle = this.props.teamDisplayName;
var popoverContent;
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 0ddd6ff4f..12227fd13 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -2,10 +2,7 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
-import UserStore from '../stores/user_store.jsx';
-import TeamStore from '../stores/team_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import AboutBuildModal from './about_build_modal.jsx';
import TeamMembersModal from './team_members_modal.jsx';
@@ -15,38 +12,20 @@ import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import Constants from '../utils/constants.jsx';
import {FormattedMessage} from 'mm-intl';
-
-function getStateFromStores() {
- const teams = [];
- const teamsObject = UserStore.getTeams();
- for (const teamId in teamsObject) {
- if (teamsObject.hasOwnProperty(teamId)) {
- teams.push(teamsObject[teamId]);
- }
- }
-
- teams.sort(Utils.sortByDisplayName);
- return {teams};
-}
+import {Link} from 'react-router';
export default class NavbarDropdown extends React.Component {
constructor(props) {
super(props);
this.blockToggle = false;
- this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.handleAboutModal = this.handleAboutModal.bind(this);
- this.onListenerChange = this.onListenerChange.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
- const state = getStateFromStores();
- state.showUserSettingsModal = false;
- state.showAboutModal = false;
- this.state = state;
- }
- handleLogoutClick(e) {
- e.preventDefault();
- client.logout();
+ this.state = {
+ showUserSettingsModal: false,
+ showAboutModal: false
+ };
}
handleAboutModal() {
this.setState({showAboutModal: true});
@@ -55,9 +34,6 @@ export default class NavbarDropdown extends React.Component {
this.setState({showAboutModal: false});
}
componentDidMount() {
- UserStore.addTeamsChangeListener(this.onListenerChange);
- TeamStore.addChangeListener(this.onListenerChange);
-
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
$('.sidebar--left .dropdown-menu').scrollTop(0);
this.blockToggle = true;
@@ -67,24 +43,15 @@ export default class NavbarDropdown extends React.Component {
});
}
componentWillUnmount() {
- UserStore.removeTeamsChangeListener(this.onListenerChange);
- TeamStore.removeChangeListener(this.onListenerChange);
-
$(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
}
- onListenerChange() {
- var newState = getStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
render() {
var teamLink = '';
var inviteLink = '';
var manageLink = '';
var sysAdminLink = '';
var adminDivider = '';
- var currentUser = UserStore.getCurrentUser();
+ var currentUser = this.props.currentUser;
var isAdmin = false;
var isSystemAdmin = false;
var teamSettings = null;
@@ -97,7 +64,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- onClick={EventHelpers.showInviteMemberModal}
+ onClick={GlobalActions.showInviteMemberModal}
>
<FormattedMessage
id='navbar_dropdown.inviteMember'
@@ -112,7 +79,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- onClick={EventHelpers.showGetTeamInviteLinkModal}
+ onClick={GlobalActions.showGetTeamInviteLinkModal}
>
<FormattedMessage
id='navbar_dropdown.teamLink'
@@ -158,7 +125,7 @@ export default class NavbarDropdown extends React.Component {
sysAdminLink = (
<li>
<a
- href={'/admin_console?' + Utils.getSessionIndex()}
+ href={'/admin_console'}
>
<FormattedMessage
id='navbar_dropdown.console'
@@ -171,31 +138,6 @@ export default class NavbarDropdown extends React.Component {
var teams = [];
- if (this.state.teams.length > 1) {
- teams.push(
- <li
- className='divider'
- key='div'
- >
- </li>
- );
-
- this.state.teams.forEach((team) => {
- if (team.name !== this.props.teamName) {
- teams.push(
- <li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>
- <FormattedMessage
- id='navbar_dropdown.switchTeam'
- defaultMessage='Switch to {team}'
- values={{
- team: team.display_name
- }}
- />
- </a></li>);
- }
- });
- }
-
if (global.window.mm_config.EnableTeamCreation === 'true') {
teams.push(
<li key='newTeam_li'>
@@ -283,15 +225,12 @@ export default class NavbarDropdown extends React.Component {
{inviteLink}
{teamLink}
<li>
- <a
- href='#'
- onClick={this.handleLogoutClick}
- >
+ <Link to={'/' + this.props.teamName + '/logout'}>
<FormattedMessage
id='navbar_dropdown.logout'
defaultMessage='Logout'
/>
- </a>
+ </Link>
</li>
{adminDivider}
{teamSettings}
@@ -333,5 +272,6 @@ NavbarDropdown.defaultProps = {
NavbarDropdown.propTypes = {
teamType: React.PropTypes.string,
teamDisplayName: React.PropTypes.string,
- teamName: React.PropTypes.string
+ teamName: React.PropTypes.string,
+ currentUser: React.PropTypes.object
};
diff --git a/web/react/components/needs_team.jsx b/web/react/components/needs_team.jsx
new file mode 100644
index 000000000..33b9cd37e
--- /dev/null
+++ b/web/react/components/needs_team.jsx
@@ -0,0 +1,20 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+
+export default class NeedsTeam extends React.Component {
+ componentWillMount() {
+ GlobalActions.loadTeamRequiredPage();
+ }
+ render() {
+ return this.props.children;
+ }
+}
+
+NeedsTeam.defaultProps = {
+};
+
+NeedsTeam.propTypes = {
+ children: React.PropTypes.object
+};
diff --git a/web/react/components/not_logged_in.jsx b/web/react/components/not_logged_in.jsx
new file mode 100644
index 000000000..7af293e77
--- /dev/null
+++ b/web/react/components/not_logged_in.jsx
@@ -0,0 +1,70 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+
+export default class NotLoggedIn extends React.Component {
+ componentDidMount() {
+ $('body').attr('class', 'white');
+ $('#root').attr('class', 'container-fluid');
+ }
+ componentWillUnmount() {
+ $('body').attr('class', '');
+ $('#root').attr('class', '');
+ }
+ render() {
+ return (
+ <div className='inner__wrap'>
+ <div className='row content'>
+ {this.props.children}
+ <div className='footer-push'></div>
+ </div>
+ <div className='row footer'>
+ <div className='footer-pane col-xs-12'>
+ <div className='col-xs-12'>
+ <span className='pull-right footer-site-name'>{global.window.mm_config.SiteName}</span>
+ </div>
+ <div className='col-xs-12'>
+ <span className='pull-right footer-link copyright'>{'© 2015 Mattermost, Inc.'}</span>
+ <a
+ id='help_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.HelpLink}
+ >
+ <FormattedMessage id='web.footer.help'/>
+ </a>
+ <a
+ id='terms_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.TermsOfServiceLink}
+ >
+ <FormattedMessage id='web.footer.terms'/>
+ </a>
+ <a
+ id='privacy_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.PrivacyPolicyLink}
+ >
+ <FormattedMessage id='web.footer.privacy'/>
+ </a>
+ <a
+ id='about_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.AboutLink}
+ >
+ <FormattedMessage id='web.footer.about'/>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+NotLoggedIn.defaultProps = {
+};
+
+NotLoggedIn.propTypes = {
+ children: React.PropTypes.object
+};
diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx
deleted file mode 100644
index 4c9bb6310..000000000
--- a/web/react/components/password_reset.jsx
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PasswordResetSendLink from './password_reset_send_link.jsx';
-import PasswordResetForm from './password_reset_form.jsx';
-
-export default class PasswordReset extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {};
- }
- render() {
- if (this.props.isReset === 'false') {
- return (
- <PasswordResetSendLink
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- />
- );
- }
-
- return (
- <PasswordResetForm
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- hash={this.props.hash}
- data={this.props.data}
- />
- );
- }
-}
-
-PasswordReset.defaultProps = {
- isReset: '',
- teamName: '',
- teamDisplayName: '',
- hash: '',
- data: ''
-};
-PasswordReset.propTypes = {
- isReset: React.PropTypes.string,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
- hash: React.PropTypes.string,
- data: React.PropTypes.string
-};
diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx
index 380dbe973..cfd39e440 100644
--- a/web/react/components/password_reset_form.jsx
+++ b/web/react/components/password_reset_form.jsx
@@ -2,24 +2,11 @@
// See License.txt for license information.
import * as Client from '../utils/client.jsx';
+import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
-import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
-
-const holders = defineMessages({
- error: {
- id: 'password_form.error',
- defaultMessage: 'Please enter at least {chars} characters.'
- },
- update: {
- id: 'password_form.update',
- defaultMessage: 'Your password has been updated successfully.'
- },
- pwd: {
- id: 'password_form.pwd',
- defaultMessage: 'Password'
- }
-});
+import {FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
class PasswordResetForm extends React.Component {
constructor(props) {
@@ -32,51 +19,50 @@ class PasswordResetForm extends React.Component {
handlePasswordReset(e) {
e.preventDefault();
- const {formatMessage} = this.props.intl;
- var state = {};
-
- var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
+ const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- state.error = formatMessage(holders.error, {chars: Constants.MIN_PASSWORD_LENGTH});
- this.setState(state);
+ this.setState({
+ error: (
+ <FormattedMessage
+ id='password_form.error'
+ defaultMessage='Please enter at least {chars} characters.'
+ chars={Constants.MIN_PASSWORD_LENGTH}
+ />
+ )
+ });
return;
}
- state.error = null;
- this.setState(state);
+ this.setState({
+ error: null
+ });
- var data = {};
+ const data = {};
data.new_password = password;
- data.hash = this.props.hash;
- data.data = this.props.data;
- data.name = this.props.teamName;
+ data.hash = this.props.location.query.h;
+ data.data = this.props.location.query.d;
+ data.name = this.props.params.team;
Client.resetPassword(data,
- function resetSuccess() {
- this.setState({error: null, updateText: formatMessage(holders.update)});
- }.bind(this),
- function resetFailure(err) {
- this.setState({error: err.message, updateText: null});
- }.bind(this)
+ () => {
+ this.setState({error: null});
+ browserHistory.push('/' + this.props.params.team + '/login');
+ },
+ (err) => {
+ this.setState({error: err.message});
+ }
);
}
render() {
- var updateText = null;
- if (this.state.updateText) {
- updateText = (<div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText}
- <FormattedHTMLMessage
- id='password_form.click'
- defaultMessage='Click <a href={url}>here</a> to log in.'
- values={{
- url: '/' + this.props.teamName + '/login'
- }}
- />
- </label></div>);
- }
-
var error = null;
if (this.state.error) {
- error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ error = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ {this.state.error}
+ </label>
+ </div>
+ );
}
var formClass = 'form-group';
@@ -84,7 +70,6 @@ class PasswordResetForm extends React.Component {
formClass += ' has-error';
}
- const {formatMessage} = this.props.intl;
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
@@ -98,9 +83,8 @@ class PasswordResetForm extends React.Component {
<p>
<FormattedMessage
id='password_form.enter'
- defaultMessage='Enter a new password for your {teamDisplayName} {siteName} account.'
+ defaultMessage='Enter a new password for your {siteName} account.'
values={{
- teamDisplayName: this.props.teamDisplayName,
siteName: global.window.mm_config.SiteName
}}
/>
@@ -111,7 +95,10 @@ class PasswordResetForm extends React.Component {
className='form-control'
name='password'
ref='password'
- placeholder={formatMessage(holders.pwd)}
+ placeholder={Utils.localizeMessage(
+ 'password_form.pwd',
+ 'Password'
+ )}
spellCheck='false'
/>
</div>
@@ -125,7 +112,6 @@ class PasswordResetForm extends React.Component {
defaultMessage='Change my password'
/>
</button>
- {updateText}
</form>
</div>
</div>
@@ -134,17 +120,10 @@ class PasswordResetForm extends React.Component {
}
PasswordResetForm.defaultProps = {
- teamName: '',
- teamDisplayName: '',
- hash: '',
- data: ''
};
PasswordResetForm.propTypes = {
- intl: intlShape.isRequired,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
- hash: React.PropTypes.string,
- data: React.PropTypes.string
+ params: React.PropTypes.object.isRequired,
+ location: React.PropTypes.object.isRequired
};
-export default injectIntl(PasswordResetForm); \ No newline at end of file
+export default PasswordResetForm;
diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx
index 8cc8a050d..ce6253e16 100644
--- a/web/react/components/password_reset_send_link.jsx
+++ b/web/react/components/password_reset_send_link.jsx
@@ -4,26 +4,7 @@
import * as Utils from '../utils/utils.jsx';
import * as client from '../utils/client.jsx';
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
-
-const holders = defineMessages({
- error: {
- id: 'password_send.error',
- defaultMessage: 'Please enter a valid email address.'
- },
- link: {
- id: 'password_send.link',
- defaultMessage: '<p>A password reset link has been sent to <b>{email}</b> for your <b>{teamDisplayName}</b> team on {hostname}.</p>'
- },
- checkInbox: {
- id: 'password_send.checkInbox',
- defaultMessage: 'Please check your inbox.'
- },
- email: {
- id: 'password_send.email',
- defaultMessage: 'Email'
- }
-});
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
class PasswordResetSendLink extends React.Component {
constructor(props) {
@@ -31,48 +12,64 @@ class PasswordResetSendLink extends React.Component {
this.handleSendLink = this.handleSendLink.bind(this);
- this.state = {};
+ this.state = {
+ error: '',
+ updateText: ''
+ };
}
handleSendLink(e) {
e.preventDefault();
- var state = {};
- const {formatMessage} = this.props.intl;
var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !Utils.isEmail(email)) {
- state.error = formatMessage(holders.error);
- this.setState(state);
+ this.setState({
+ error: (
+ <FormattedMessage
+ id={'password_send.error'}
+ defaultMessage={'Please enter a valid email address.'}
+ />
+ )
+ });
return;
}
- state.error = null;
- this.setState(state);
+ // End of error checking clear error
+ this.setState({
+ error: ''
+ });
var data = {};
data.email = email;
- data.name = this.props.teamName;
-
+ data.name = this.props.params.team;
client.sendPasswordReset(data,
- function passwordResetSent() {
- this.setState({error: null, updateText: formatMessage(holders.link, {email: email, teamDisplayName: this.props.teamDisplayName, hostname: window.location.hostname}), moreUpdateText: formatMessage(holders.checkInbox)});
- $(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
- }.bind(this),
- function passwordResetFailedToSend(err) {
- this.setState({error: err.message, update_text: null, moreUpdateText: null});
- }.bind(this)
- );
+ () => {
+ this.setState({
+ error: null,
+ updateText: (
+ <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>'
+ email={email}
+ />
+ <FormattedMessage
+ id={'password_send.checkInbox'}
+ defaultMessage={'Please check your inbox.'}
+ />
+ </div>
+ )
+ });
+ $(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
+ },
+ (err) => {
+ this.setState({
+ error: err.message,
+ update_text: null
+ });
+ }
+ );
}
render() {
- var updateText = null;
- if (this.state.updateText) {
- updateText = (
- <div className='reset-form alert alert-success'
- dangerouslySetInnerHTML={{__html: this.state.updateText + this.state.moreUpdateText}}
- >
- </div>
- );
- }
-
var error = null;
if (this.state.error) {
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
@@ -83,51 +80,60 @@ class PasswordResetSendLink extends React.Component {
formClass += ' has-error';
}
- const {formatMessage} = this.props.intl;
return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
<FormattedMessage
- id='password_send.title'
- defaultMessage='Password Reset'
+ id='web.header.back'
/>
- </h3>
- {updateText}
- <form
- onSubmit={this.handleSendLink}
- ref='reset_form'
- >
- <p>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>
<FormattedMessage
- id='password_send.description'
- defaultMessage='To reset your password, enter the email address you used to sign up for {teamName}.'
- values={{
- teamName: this.props.teamDisplayName
- }}
+ id='password_send.title'
+ defaultMessage='Password Reset'
/>
- </p>
- <div className={formClass}>
- <input
- type='email'
- className='form-control'
- name='email'
- ref='email'
- placeholder={formatMessage(holders.email)}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
+ </h3>
+ {this.state.updateText}
+ <form
+ onSubmit={this.handleSendLink}
+ ref='reset_form'
>
- <FormattedMessage
- id='password_send.reset'
- defaultMessage='Reset my password'
- />
- </button>
- </form>
+ <p>
+ <FormattedMessage
+ id='password_send.description'
+ defaultMessage='To reset your password, enter the email address you used to sign up'
+ />
+ </p>
+ <div className={formClass}>
+ <input
+ type='email'
+ className='form-control'
+ name='email'
+ ref='email'
+ placeholder={Utils.localizeMessage(
+ 'password_send.email',
+ 'Email'
+ )}
+ spellCheck='false'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='password_send.reset'
+ defaultMessage='Reset my password'
+ />
+ </button>
+ </form>
+ </div>
</div>
</div>
);
@@ -135,13 +141,9 @@ class PasswordResetSendLink extends React.Component {
}
PasswordResetSendLink.defaultProps = {
- teamName: '',
- teamDisplayName: ''
};
PasswordResetSendLink.propTypes = {
- intl: intlShape.isRequired,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ params: React.PropTypes.object.isRequired
};
-export default injectIntl(PasswordResetSendLink); \ No newline at end of file
+export default PasswordResetSendLink;
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index afff78bae..1943fb409 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -118,7 +118,7 @@ export default class PopoverListMembers extends React.Component {
className='profile-img rounded pull-left'
width='26px'
height='26px'
- src={`/api/v1/users/${m.id}/image?time=${m.update_at}&${Utils.getSessionIndex()}`}
+ src={`/api/v1/users/${m.id}/image?time=${m.update_at}`}
/>
<div className='pull-left'>
<div
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 889d4311e..3a855edf2 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -4,7 +4,6 @@
import PostHeader from './post_header.jsx';
import PostBody from './post_body.jsx';
-import UserStore from '../stores/user_store.jsx';
import PostStore from '../stores/post_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -98,7 +97,7 @@ export default class Post extends React.Component {
return true;
}
- if (nextProps.hasProfiles !== this.props.hasProfiles) {
+ if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true;
}
@@ -128,7 +127,6 @@ export default class Post extends React.Component {
const post = this.props.post;
const parentPost = this.props.parentPost;
const posts = this.props.posts;
- const user = this.props.user || {};
if (!post.props) {
post.props = {};
@@ -156,13 +154,15 @@ export default class Post extends React.Component {
}
let currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id && !post.props.from_webhook && !Utils.isSystemMessage(post)) {
+ if (this.props.currentUser.id === post.user_id && !post.props.from_webhook && !Utils.isSystemMessage(post)) {
currentUserCss = 'current--user';
}
- let timestamp = user.update_at;
- if (timestamp == null) {
- timestamp = UserStore.getCurrentUser().update_at;
+ let timestamp = 0;
+ if (!this.props.user || this.props.user.update_at == null) {
+ timestamp = this.props.currentUser.update_at;
+ } else {
+ timestamp = this.props.user.update_at;
}
let sameUserClass = '';
@@ -182,7 +182,7 @@ export default class Post extends React.Component {
let profilePic = null;
if (!this.props.hideProfilePic) {
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex();
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
@@ -218,6 +218,7 @@ export default class Post extends React.Component {
isLastComment={this.props.isLastComment}
sameUser={this.props.sameUser}
user={this.props.user}
+ currentUser={this.props.currentUser}
/>
<PostBody
post={post}
@@ -226,7 +227,6 @@ export default class Post extends React.Component {
posts={posts}
handleCommentClick={this.handleCommentClick}
retryPost={this.retryPost}
- hasProfiles={this.props.hasProfiles}
/>
</div>
</div>
@@ -247,5 +247,6 @@ Post.propTypes = {
isLastComment: React.PropTypes.bool,
shouldHighlight: React.PropTypes.bool,
displayNameType: React.PropTypes.string,
- hasProfiles: React.PropTypes.bool
+ hasProfiles: React.PropTypes.bool,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 70cf86748..2fa4cebfe 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -80,12 +80,10 @@ class PostBody extends React.Component {
username = parentPost.props.override_username;
}
- if (global.window.mm_locale === 'en') {
- if (username.slice(-1) === 's') {
- apostrophe = '\'';
- } else {
- apostrophe = '\'s';
- }
+ if (username.slice(-1) === 's') {
+ apostrophe = '\'';
+ } else {
+ apostrophe = '\'s';
}
name = (
<a
@@ -215,8 +213,7 @@ PostBody.propTypes = {
post: React.PropTypes.object.isRequired,
parentPost: React.PropTypes.object,
retryPost: React.PropTypes.func.isRequired,
- handleCommentClick: React.PropTypes.func.isRequired,
- hasProfiles: React.PropTypes.bool
+ handleCommentClick: React.PropTypes.func.isRequired
};
export default injectIntl(PostBody);
diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx
index c2a928f3b..70b3c8dbf 100644
--- a/web/react/components/post_body_additional_content.jsx
+++ b/web/react/components/post_body_additional_content.jsx
@@ -112,24 +112,32 @@ export default class PostBodyAdditionalContent extends React.Component {
}
render() {
- var generateEmbed = this.generateEmbed();
+ const generateEmbed = this.generateEmbed();
+
if (generateEmbed) {
- return (
- <div>
+ let toggle;
+ if (Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_TOGGLE)) {
+ toggle = (
<a className='post__embed-visibility'
data-expanded={this.state.embedVisible}
aria-label='Toggle Embed Visibility'
onClick={this.toggleEmbedVisibility}
- >
- </a>
+ />
+ );
+ }
+
+ return (
+ <div>
+ {toggle}
<div className='post__embed-container'
hidden={!this.state.embedVisible}
>
{generateEmbed}
</div>
</div>
- );
+ );
}
+
return null;
}
}
diff --git a/web/react/components/post_focus_view.jsx b/web/react/components/post_focus_view.jsx
index 44a0bae09..fd654f502 100644
--- a/web/react/components/post_focus_view.jsx
+++ b/web/react/components/post_focus_view.jsx
@@ -5,7 +5,8 @@ import PostsView from './posts_view.jsx';
import PostStore from '../stores/post_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import UserStore from '../stores/user_store.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -15,6 +16,7 @@ export default class PostFocusView extends React.Component {
this.onChannelChange = this.onChannelChange.bind(this);
this.onPostsChange = this.onPostsChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
@@ -26,18 +28,21 @@ export default class PostFocusView extends React.Component {
scrollPostId: focusedPostId,
postList: PostStore.getVisiblePosts(focusedPostId),
atTop: PostStore.getVisibilityAtTop(focusedPostId),
- atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
+ atBottom: PostStore.getVisibilityAtBottom(focusedPostId),
+ currentUser: UserStore.getCurrentUser()
};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onChannelChange);
PostStore.addChangeListener(this.onPostsChange);
+ UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
PostStore.removeChangeListener(this.onPostsChange);
+ UserStore.removeChangeListener(this.onUserChange);
}
onChannelChange() {
@@ -46,6 +51,10 @@ export default class PostFocusView extends React.Component {
});
}
+ onUserChange() {
+ this.setState({currentUser: UserStore.getCurrentUser()});
+ }
+
onPostsChange() {
const focusedPostId = PostStore.getFocusedPostId();
if (focusedPostId == null) {
@@ -65,11 +74,11 @@ export default class PostFocusView extends React.Component {
}
loadMorePostsTop() {
- EventHelpers.emitLoadMorePostsFocusedTopEvent();
+ GlobalActions.emitLoadMorePostsFocusedTopEvent();
}
loadMorePostsBottom() {
- EventHelpers.emitLoadMorePostsFocusedBottomEvent();
+ GlobalActions.emitLoadMorePostsFocusedBottomEvent();
}
getIntroMessage() {
@@ -89,6 +98,10 @@ export default class PostFocusView extends React.Component {
const postsToHighlight = {};
postsToHighlight[this.state.scrollPostId] = true;
+ if (!this.state.currentUser || !this.state.postList) {
+ return null;
+ }
+
return (
<div id='post-list'>
<PostsView
@@ -106,6 +119,7 @@ export default class PostFocusView extends React.Component {
messageSeparatorTime={0}
postsToHighlight={postsToHighlight}
profiles={this.props.profiles}
+ currentUser={this.state.currentUser}
/>
</div>
);
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx
index 2803fe387..966775dad 100644
--- a/web/react/components/post_header.jsx
+++ b/web/react/components/post_header.jsx
@@ -14,16 +14,15 @@ export default class PostHeader extends React.Component {
}
render() {
const post = this.props.post;
- const user = this.props.user;
- let userProfile = <UserProfile user={user}/>;
+ let userProfile = <UserProfile user={this.props.user}/>;
let botIndicator;
if (post.props && post.props.from_webhook) {
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
userProfile = (
<UserProfile
- user={user}
+ user={this.props.user}
overwriteName={post.props.override_username}
disablePopover={true}
/>
@@ -54,6 +53,7 @@ export default class PostHeader extends React.Component {
allowReply='true'
isLastComment={this.props.isLastComment}
sameUser={this.props.sameUser}
+ currentUser={this.props.currentUser}
/>
</li>
</ul>
@@ -68,10 +68,11 @@ PostHeader.defaultProps = {
sameUser: false
};
PostHeader.propTypes = {
- post: React.PropTypes.object,
+ post: React.PropTypes.object.isRequired,
user: React.PropTypes.object,
- commentCount: React.PropTypes.number,
- isLastComment: React.PropTypes.bool,
- handleCommentClick: React.PropTypes.func,
- sameUser: React.PropTypes.bool
+ currentUser: React.PropTypes.object.isRequired,
+ commentCount: React.PropTypes.number.isRequired,
+ isLastComment: React.PropTypes.bool.isRequired,
+ handleCommentClick: React.PropTypes.func.isRequired,
+ sameUser: React.PropTypes.bool.isRequired
};
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index ffac6eaef..d0a4c828e 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -1,10 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
import TimeSince from './time_since.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
@@ -27,8 +26,8 @@ export default class PostInfo extends React.Component {
}
createDropdown() {
var post = this.props.post;
- var isOwner = UserStore.getCurrentId() === post.user_id;
- var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles);
+ var isOwner = this.props.currentUser.id === post.user_id;
+ var isAdmin = Utils.isAdmin(this.props.currentUser.roles);
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || Utils.isPostEphemeral(post)) {
return '';
@@ -47,21 +46,21 @@ export default class PostInfo extends React.Component {
if (this.props.allowReply === 'true') {
dropdownContents.push(
- <li
- key='replyLink'
- role='presentation'
- >
- <a
- className='link__reply theme'
- href='#'
- onClick={this.props.handleCommentClick}
- >
- <FormattedMessage
- id='post_info.reply'
- defaultMessage='Reply'
- />
- </a>
- </li>
+ <li
+ key='replyLink'
+ role='presentation'
+ >
+ <a
+ className='link__reply theme'
+ href='#'
+ onClick={this.props.handleCommentClick}
+ >
+ <FormattedMessage
+ id='post_info.reply'
+ defaultMessage='Reply'
+ />
+ </a>
+ </li>
);
}
@@ -93,7 +92,7 @@ export default class PostInfo extends React.Component {
<a
href='#'
role='menuitem'
- onClick={() => EventHelpers.showDeletePostModal(post, dataComments)}
+ onClick={() => GlobalActions.showDeletePostModal(post, dataComments)}
>
<FormattedMessage
id='post_info.del'
@@ -157,11 +156,11 @@ export default class PostInfo extends React.Component {
handlePermalink(e) {
e.preventDefault();
- EventHelpers.showGetPostLinkModal(this.props.post);
+ GlobalActions.showGetPostLinkModal(this.props.post);
}
removePost() {
- EventHelpers.emitRemovePost(this.props.post);
+ GlobalActions.emitRemovePost(this.props.post);
}
createRemovePostButton(post) {
if (!Utils.isPostEphemeral(post)) {
@@ -240,10 +239,11 @@ PostInfo.defaultProps = {
sameUser: false
};
PostInfo.propTypes = {
- post: React.PropTypes.object,
- commentCount: React.PropTypes.number,
- isLastComment: React.PropTypes.bool,
- allowReply: React.PropTypes.string,
- handleCommentClick: React.PropTypes.func,
- sameUser: React.PropTypes.bool
+ post: React.PropTypes.object.isRequired,
+ commentCount: React.PropTypes.number.isRequired,
+ isLastComment: React.PropTypes.bool.isRequired,
+ allowReply: React.PropTypes.string.isRequired,
+ handleCommentClick: React.PropTypes.func.isRequired,
+ sameUser: React.PropTypes.bool.isRequired,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index 9a1673483..0a9232850 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -1,9 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import UserStore from '../stores/user_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as Utils from '../utils/utils.jsx';
import Post from './post.jsx';
import Constants from '../utils/constants.jsx';
@@ -144,7 +143,7 @@ export default class PostsView extends React.Component {
createPosts(posts, order) {
const postCtls = [];
let previousPostDay = new Date(0);
- const userId = UserStore.getCurrentId();
+ const userId = this.props.currentUser.id;
const profiles = this.props.profiles || {};
let renderedLastViewed = false;
@@ -230,8 +229,8 @@ export default class PostsView extends React.Component {
const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
let profile;
- if (UserStore.getCurrentId() === post.user_id) {
- profile = UserStore.getCurrentUser();
+ if (this.props.currentUser.id === post.user_id) {
+ profile = this.props.currentUser;
} else {
profile = profiles[post.user_id];
}
@@ -248,10 +247,10 @@ export default class PostsView extends React.Component {
hideProfilePic={hideProfilePic}
isLastComment={isLastComment}
shouldHighlight={shouldHighlight}
- onClick={() => EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
+ onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
displayNameType={this.state.displayNameType}
- hasProfiles={profiles && Object.keys(profiles).length > 1}
user={profile}
+ currentUser={this.props.currentUser}
/>
);
@@ -526,7 +525,7 @@ PostsView.defaultProps = {
PostsView.propTypes = {
isActive: React.PropTypes.bool,
postList: React.PropTypes.object,
- profiles: React.PropTypes.object,
+ profiles: React.PropTypes.object.isRequired,
scrollPostId: React.PropTypes.string,
scrollType: React.PropTypes.number,
postViewScrolled: React.PropTypes.func.isRequired,
@@ -536,7 +535,8 @@ PostsView.propTypes = {
showMoreMessagesBottom: React.PropTypes.bool,
introText: React.PropTypes.element,
messageSeparatorTime: React.PropTypes.number,
- postsToHighlight: React.PropTypes.object
+ postsToHighlight: React.PropTypes.object,
+ currentUser: React.PropTypes.object.isRequired
};
function FloatingTimestamp({isScrolling, post}) {
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 92d658b55..b361779d2 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -6,9 +6,10 @@ import LoadingScreen from './loading_screen.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import PostStore from '../stores/post_store.jsx';
+import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
@@ -21,6 +22,7 @@ export default class PostsViewContainer extends React.Component {
this.onChannelChange = this.onChannelChange.bind(this);
this.onChannelLeave = this.onChannelLeave.bind(this);
this.onPostsChange = this.onPostsChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
@@ -28,7 +30,8 @@ export default class PostsViewContainer extends React.Component {
const currentChannelId = ChannelStore.getCurrentId();
const state = {
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
- scrollPost: null
+ scrollPost: null,
+ currentUser: UserStore.getCurrentUser()
};
if (currentChannelId) {
Object.assign(state, {
@@ -54,12 +57,17 @@ export default class PostsViewContainer extends React.Component {
ChannelStore.addLeaveListener(this.onChannelLeave);
PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
+ UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
ChannelStore.removeLeaveListener(this.onChannelLeave);
PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
+ UserStore.removeChangeListener(this.onUserChange);
+ }
+ onUserChange() {
+ this.setState({currentUser: UserStore.getCurrentUser()});
}
handlePostsViewJumpRequest(type, post) {
switch (type) {
@@ -139,7 +147,7 @@ export default class PostsViewContainer extends React.Component {
return PostStore.getVisiblePosts(id);
}
loadMorePostsTop() {
- EventHelpers.emitLoadMorePostsEvent();
+ GlobalActions.emitLoadMorePostsEvent();
}
handlePostsViewScroll(atBottom) {
if (atBottom) {
@@ -149,11 +157,15 @@ export default class PostsViewContainer extends React.Component {
}
}
shouldComponentUpdate(nextProps, nextState) {
- if (Utils.areObjectsEqual(this.state, nextState)) {
- return false;
+ if (!Utils.areObjectsEqual(this.state, nextState)) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(this.props, nextProps)) {
+ return true;
}
- return true;
+ return false;
}
render() {
const postLists = this.state.postLists;
@@ -161,6 +173,10 @@ export default class PostsViewContainer extends React.Component {
const currentChannelId = channels[this.state.currentChannelIndex];
const channel = ChannelStore.get(currentChannelId);
+ if (!this.state.currentUser || !channel) {
+ return null;
+ }
+
const postListCtls = [];
for (let i = 0; i < channels.length; i++) {
const isActive = (channels[i] === currentChannelId);
@@ -181,6 +197,7 @@ export default class PostsViewContainer extends React.Component {
introText={channel ? createChannelIntroMessage(channel) : null}
messageSeparatorTime={this.state.currentLastViewed}
profiles={this.props.profiles}
+ currentUser={this.state.currentUser}
/>
);
if (!postLists[i] && isActive) {
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 9588809eb..9183b761f 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -14,7 +14,7 @@ import * as AsyncClient from '../utils/async_client.jsx';
var ActionTypes = Constants.ActionTypes;
import * as TextFormatting from '../utils/text_formatting.jsx';
import twemoji from 'twemoji';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
@@ -70,7 +70,7 @@ class RhsComment extends React.Component {
}
handlePermalink(e) {
e.preventDefault();
- EventHelpers.showGetPostLinkModal(this.props.post);
+ GlobalActions.showGetPostLinkModal(this.props.post);
}
componentDidMount() {
this.parseEmojis();
@@ -151,7 +151,7 @@ class RhsComment extends React.Component {
<a
href='#'
role='menuitem'
- onClick={() => EventHelpers.showDeletePostModal(post, 0)}
+ onClick={() => GlobalActions.showDeletePostModal(post, 0)}
>
<FormattedMessage
id='rhs_comment.del'
@@ -253,7 +253,7 @@ class RhsComment extends React.Component {
<div className='post__content'>
<div className='post__img'>
<img
- src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
height='36'
width='36'
/>
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index 023f3dd2d..fc1cd0b41 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -10,7 +10,7 @@ import * as Emoji from '../utils/emoticons.jsx';
import FileAttachmentList from './file_attachment_list.jsx';
import twemoji from 'twemoji';
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
@@ -34,7 +34,7 @@ export default class RhsRootPost extends React.Component {
}
handlePermalink(e) {
e.preventDefault();
- EventHelpers.showGetPostLinkModal(this.props.post);
+ GlobalActions.showGetPostLinkModal(this.props.post);
}
componentDidMount() {
this.parseEmojis();
@@ -142,7 +142,7 @@ export default class RhsRootPost extends React.Component {
<a
href='#'
role='menuitem'
- onClick={() => EventHelpers.showDeletePostModal(post, this.props.commentCount)}
+ onClick={() => GlobalActions.showDeletePostModal(post, this.props.commentCount)}
>
<FormattedMessage
id='rhs_root.del'
@@ -211,7 +211,7 @@ export default class RhsRootPost extends React.Component {
);
}
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex();
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
diff --git a/web/react/components/root.jsx b/web/react/components/root.jsx
new file mode 100644
index 000000000..70038203b
--- /dev/null
+++ b/web/react/components/root.jsx
@@ -0,0 +1,90 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import BrowserStore from '../stores/browser_store.jsx';
+import LocalizationStore from '../stores/localization_store.jsx';
+
+var IntlProvider = ReactIntl.IntlProvider;
+
+export default class Root extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ locale: 'en',
+ translations: null
+ };
+
+ this.localizationChanged = this.localizationChanged.bind(this);
+ }
+ localizationChanged() {
+ this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()});
+ }
+ componentWillMount() {
+ // Setup localization listener
+ LocalizationStore.addChangeListener(this.localizationChanged);
+
+ // Browser store check version
+ BrowserStore.checkVersion();
+
+ window.onerror = (msg, url, line, column, stack) => {
+ var l = {};
+ l.level = 'ERROR';
+ l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
+
+ $.ajax({
+ url: '/api/v1/admin/log_client',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(l)
+ });
+
+ if (window.mm_config.EnableDeveloper === 'true') {
+ window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'});
+ window.ErrorStore.emitChange();
+ }
+ };
+
+ // Ya....
+ /*eslint-disable */
+ if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
+ !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
+ analytics.load(window.mm_config.SegmentDeveloperKey);
+ analytics.page();
+ }}();
+ } else {
+ global.window.analytics = {};
+ global.window.analytics.page = function(){};
+ global.window.analytics.track = function(){};
+ }
+ /*eslint-enable */
+
+ // Get our localizaiton
+ GlobalActions.newLocalizationSelected('en');
+ }
+ componentWillUnmount() {
+ LocalizationStore.removeChangeListener(this.localizationChanged);
+ }
+ render() {
+ if (this.state.translations == null) {
+ return <div/>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.state.locale}
+ messages={this.state.translations}
+ key={this.state.locale}
+ >
+ {this.props.children}
+ </IntlProvider>
+ );
+ }
+}
+Root.defaultProps = {
+};
+
+Root.propTypes = {
+ children: React.PropTypes.object
+};
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 05292b7b3..3a091bdd1 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -3,8 +3,7 @@
import UserStore from '../stores/user_store.jsx';
import UserProfile from './user_profile.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-import * as utils from '../utils/utils.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as TextFormatting from '../utils/text_formatting.jsx';
import Constants from '../utils/constants.jsx';
@@ -22,7 +21,7 @@ export default class SearchResultsItem extends React.Component {
handleClick(e) {
e.preventDefault();
- EventHelpers.emitPostFocusEvent(this.props.post.id);
+ GlobalActions.emitPostFocusEvent(this.props.post.id);
if ($(window).width() < 768) {
$('.sidebar--right').removeClass('move--left');
@@ -32,7 +31,7 @@ export default class SearchResultsItem extends React.Component {
handleFocusRHSClick(e) {
e.preventDefault();
- EventHelpers.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
+ GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
}
render() {
@@ -78,7 +77,7 @@ export default class SearchResultsItem extends React.Component {
<div className='post__content'>
<div className='post__img'>
<img
- src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()}
+ src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp}
height='36'
width='36'
/>
@@ -123,6 +122,7 @@ export default class SearchResultsItem extends React.Component {
</ul>
<div className='search-item-snippet'>
<span
+ onClick={TextFormatting.handleClick}
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
/>
</div>
diff --git a/web/react/components/should_verify_email.jsx b/web/react/components/should_verify_email.jsx
new file mode 100644
index 000000000..c473fe366
--- /dev/null
+++ b/web/react/components/should_verify_email.jsx
@@ -0,0 +1,111 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+import * as Client from '../utils/client.jsx';
+
+export default class ShouldVerifyEmail extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleResend = this.handleResend.bind(this);
+
+ this.state = {
+ resendStatus: 'none'
+ };
+ }
+ handleResend() {
+ const teamName = this.props.location.query.teamname;
+ const email = this.props.location.query.email;
+
+ this.setState({resendStatus: 'sending'});
+
+ Client.resendVerification(() => {
+ this.setState({resendStatus: 'success'});
+ },
+ () => {
+ this.setState({resendStatus: 'failure'});
+ },
+ teamName,
+ email);
+ }
+ render() {
+ let resendConfirm = '';
+ if (this.state.resendStatus === 'success') {
+ resendConfirm = (
+ <div>
+ <br/>
+ <p className='alert alert-success'>
+ <i className='fa fa-check'/>
+ <FormattedMessage
+ id='email_verify.sent'
+ defaultMessage=' Verification email sent.'
+ />
+ </p>
+ </div>
+ );
+ }
+
+ if (this.state.resendStatus === 'failure') {
+ resendConfirm = (
+ <div>
+ <br/>
+ <p className='alert alert-danger'>
+ <i className='fa fa-times'/>
+ <FormattedMessage id='email_verify.failed'/>
+ </p>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='email_verify.almost'
+ defaultMessage='{siteName}: You are almost done'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h3>
+ <div>
+ <p>
+ <FormattedMessage
+ id='email_verify.notVerifiedBody'
+ defaultMessage='Please verify your email address. Check your inbox for an email.'
+ />
+ </p>
+ <button
+ onClick={this.handleResend}
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='email_verify.resend'
+ defaultMessage='Resend Email'
+ />
+ </button>
+ {resendConfirm}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+ShouldVerifyEmail.defaultProps = {
+};
+ShouldVerifyEmail.propTypes = {
+ location: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index c7dba306b..5c682d64b 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -129,7 +129,9 @@ export default class Sidebar extends React.Component {
directChannels,
hiddenDirectChannelCount,
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
- showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER
+ showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
+ currentTeam: TeamStore.getCurrent(),
+ currentUser: UserStore.getCurrentUser()
};
}
@@ -179,7 +181,7 @@ export default class Sidebar extends React.Component {
}
updateTitle() {
const channel = ChannelStore.getCurrent();
- if (channel) {
+ if (channel && this.state.currentTeam) {
let currentSiteName = '';
if (global.window.mm_config.SiteName != null) {
currentSiteName = global.window.mm_config.SiteName;
@@ -196,7 +198,7 @@ export default class Sidebar extends React.Component {
const unread = this.getTotalUnreadCount();
const mentionTitle = unread.mentions > 0 ? '(' + unread.mentions + ') ' : '';
const unreadTitle = unread.msgs > 0 ? '* ' : '';
- document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + TeamStore.getCurrent().display_name + ' ' + currentSiteName;
+ document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + this.state.currentTeam.display_name + ' ' + currentSiteName;
}
}
onScroll() {
@@ -401,7 +403,6 @@ export default class Sidebar extends React.Component {
// set up click handler to switch channels (or create a new channel for non-existant ones)
var handleClick = null;
var href = '#';
- var teamURL = TeamStore.getCurrentTeamUrl();
if (!channel.fake) {
handleClick = function clickHandler(e) {
@@ -413,7 +414,7 @@ export default class Sidebar extends React.Component {
e.preventDefault();
};
- } else if (channel.fake && teamURL) {
+ } else if (channel.fake) {
// It's a direct message channel that doesn't exist yet so let's create it now
var otherUserId = Utils.getUserIdFromChannelName(channel);
@@ -434,7 +435,7 @@ export default class Sidebar extends React.Component {
},
() => {
this.setState({loadingDMChannel: -1});
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ window.location.href = '/' + this.state.currentTeam.name;
}
);
}
@@ -497,6 +498,11 @@ export default class Sidebar extends React.Component {
);
}
render() {
+ // Check if we have all info needed to render
+ if (this.state.currentTeam == null || this.state.currentUser == null) {
+ return (<div/>);
+ }
+
this.badgesActive = false;
// keep track of the first and last unread channels so we can use them to set the unread indicators
@@ -586,7 +592,10 @@ export default class Sidebar extends React.Component {
);
return (
- <div>
+ <div
+ className='sidebar--left'
+ id='sidebar-left'
+ >
<NewChannelFlow
show={showChannelModal}
channelType={this.state.newChannelModalType}
@@ -598,9 +607,10 @@ export default class Sidebar extends React.Component {
/>
<SidebarHeader
- teamDisplayName={TeamStore.getCurrent().display_name}
- teamName={TeamStore.getCurrent().name}
- teamType={TeamStore.getCurrent().type}
+ teamDisplayName={this.state.currentTeam.display_name}
+ teamName={this.state.currentTeam.name}
+ teamType={this.state.currentTeam.type}
+ currentUser={this.state.currentUser}
/>
<UnreadChannelIndicator
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 45b0a5fc4..00d30948a 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -4,10 +4,8 @@
import NavbarDropdown from './navbar_dropdown.jsx';
import TutorialTip from './tutorial/tutorial_tip.jsx';
-import UserStore from '../stores/user_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
-import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
import {FormattedHTMLMessage} from 'mm-intl';
@@ -34,7 +32,7 @@ export default class SidebarHeader extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
getStateFromStores() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
+ const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, this.props.currentUser.id, 999);
return {showTutorialTip: tutorialStep === TutorialSteps.MENU_POPOVER};
}
@@ -77,7 +75,7 @@ export default class SidebarHeader extends React.Component {
);
}
render() {
- var me = UserStore.getCurrentUser();
+ var me = this.props.currentUser;
var profilePicture = null;
if (!me) {
@@ -88,7 +86,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
/>
);
}
@@ -124,6 +122,7 @@ export default class SidebarHeader extends React.Component {
teamType={this.props.teamType}
teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
+ currentUser={this.props.currentUser}
/>
</div>
);
@@ -131,11 +130,12 @@ export default class SidebarHeader extends React.Component {
}
SidebarHeader.defaultProps = {
- teamDisplayName: global.window.mm_config.SiteName,
+ teamDisplayName: '',
teamType: ''
};
SidebarHeader.propTypes = {
teamDisplayName: React.PropTypes.string,
teamName: React.PropTypes.string,
- teamType: React.PropTypes.string
+ teamType: React.PropTypes.string,
+ currentUser: React.PropTypes.object
};
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index b81c0d099..14853d3a3 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -127,8 +127,13 @@ export default class SidebarRight extends React.Component {
}
return (
- <div className='sidebar-right-container'>
- {content}
+ <div
+ className='sidebar--right'
+ id='sidebar-right'
+ >
+ <div className='sidebar-right-container'>
+ {content}
+ </div>
</div>
);
}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 4d714e9f1..c7c5bcfd6 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -5,11 +5,11 @@ import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import UserStore from '../stores/user_store.jsx';
-import * as client from '../utils/client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-import * as utils from '../utils/utils.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import * as Utils from '../utils/utils.jsx';
import {FormattedMessage} from 'mm-intl';
+import {Link} from 'react-router';
export default class SidebarRightMenu extends React.Component {
componentDidMount() {
@@ -19,18 +19,11 @@ export default class SidebarRightMenu extends React.Component {
constructor(props) {
super(props);
- this.handleLogoutClick = this.handleLogoutClick.bind(this);
-
this.state = {
showUserSettingsModal: false
};
}
- handleLogoutClick(e) {
- e.preventDefault();
- client.logout();
- }
-
render() {
var teamLink = '';
var inviteLink = '';
@@ -42,14 +35,14 @@ export default class SidebarRightMenu extends React.Component {
var isSystemAdmin = false;
if (currentUser != null) {
- isAdmin = utils.isAdmin(currentUser.roles);
- isSystemAdmin = utils.isSystemAdmin(currentUser.roles);
+ isAdmin = Utils.isAdmin(currentUser.roles);
+ isSystemAdmin = Utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
<a
href='#'
- onClick={EventHelpers.showInviteMemberModal}
+ onClick={GlobalActions.showInviteMemberModal}
>
<i className='fa fa-user'></i>
<FormattedMessage
@@ -65,7 +58,7 @@ export default class SidebarRightMenu extends React.Component {
<li>
<a
href='#'
- onClick={EventHelpers.showGetTeamInviteLinkModal}
+ onClick={GlobalActions.showGetTeamInviteLinkModal}
>
<i className='glyphicon glyphicon-link'></i>
<FormattedMessage
@@ -107,13 +100,13 @@ export default class SidebarRightMenu extends React.Component {
);
}
- if (isSystemAdmin && !utils.isMobile()) {
+ if (isSystemAdmin && !Utils.isMobile()) {
consoleLink = (
<li>
<a
- href={'/admin_console?' + utils.getSessionIndex()}
+ href={'/admin_console'}
>
- <i className='fa fa-wrench'></i>
+ <i className='fa fa-wrench'></i>
<FormattedMessage
id='sidebar_right_menu.console'
defaultMessage='System Console'
@@ -168,7 +161,10 @@ export default class SidebarRightMenu extends React.Component {
);
}
return (
- <div>
+ <div
+ className='sidebar--menu'
+ id='sidebar-menu'
+ >
<div className='team__header theme'>
<a
className='team__name'
@@ -196,16 +192,13 @@ export default class SidebarRightMenu extends React.Component {
{manageLink}
{consoleLink}
<li>
- <a
- href='#'
- onClick={this.handleLogoutClick}
- >
+ <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}>
<i className='fa fa-sign-out'></i>
<FormattedMessage
id='sidebar_right_menu.logout'
defaultMessage='Logout'
/>
- </a>
+ </Link>
</li>
<li className='divider'></li>
{helpLink}
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 26c46dad0..2adf8d111 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -6,6 +6,8 @@ import EmailSignUpPage from './team_signup_with_email.jsx';
import SSOSignupPage from './team_signup_with_sso.jsx';
import LdapSignUpPage from './team_signup_with_ldap.jsx';
import Constants from '../utils/constants.jsx';
+import TeamStore from '../stores/team_store.jsx';
+import * as AsyncClient from '../utils/async_client.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -14,6 +16,7 @@ export default class TeamSignUp extends React.Component {
super(props);
this.updatePage = this.updatePage.bind(this);
+ this.onTeamUpdate = this.onTeamUpdate.bind(this);
var count = 0;
@@ -46,11 +49,34 @@ export default class TeamSignUp extends React.Component {
this.setState({page});
}
+ componentWillMount() {
+ if (global.window.mm_config.EnableTeamListing === 'true') {
+ AsyncClient.getAllTeams();
+ this.onTeamUpdate();
+ }
+ }
+
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamUpdate);
+ }
+
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamUpdate);
+ }
+
+ onTeamUpdate() {
+ this.setState({
+ teams: TeamStore.getAll()
+ });
+ }
+
render() {
- var teamListing = null;
+ let teamListing = null;
if (global.window.mm_config.EnableTeamListing === 'true') {
- if (this.props.teams.length === 0) {
+ if (this.state.teams == null) {
+ teamListing = (<div/>);
+ } else if (this.state.teams.length === 0) {
if (global.window.mm_config.EnableTeamCreation !== 'true') {
teamListing = (
<div>
@@ -72,23 +98,26 @@ export default class TeamSignUp extends React.Component {
</h4>
<div className='signup-team-all'>
{
- this.props.teams.map((team) => {
- return (
- <div
- key={'team_' + team.name}
- className='signup-team-dir'
- >
- <a
- href={'/' + team.name}
+ Object.values(this.state.teams).map((team) => {
+ if (team.allow_team_listing) {
+ return (
+ <div
+ key={'team_' + team.name}
+ className='signup-team-dir'
>
- <span className='signup-team-dir__name'>{team.display_name}</span>
- <span
- className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
- aria-hidden='true'
- />
- </a>
- </div>
- );
+ <a
+ href={'/' + team.name}
+ >
+ <span className='signup-team-dir__name'>{team.display_name}</span>
+ <span
+ className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
+ aria-hidden='true'
+ />
+ </a>
+ </div>
+ );
+ }
+ return null;
})
}
</div>
@@ -103,42 +132,26 @@ export default class TeamSignUp extends React.Component {
}
}
+ let signupMethod = null;
+
if (global.window.mm_config.EnableTeamCreation !== 'true') {
if (teamListing == null) {
- return (
- <div>
- <FormattedMessage
- id='signup_team.disabled'
- defaultMessage='Team creation has been disabled. Please contact an administrator for access.'
- />
- </div>
+ signupMethod = (
+ <FormattedMessage
+ id='signup_team.disabled'
+ defaultMessage='Team creation has been disabled. Please contact an administrator for access.'
+ />
);
}
-
- return (
- <div>
- {teamListing}
- </div>
- );
- }
-
- if (this.state.page === 'choose') {
- return (
- <div>
- {teamListing}
- <ChoosePage
- updatePage={this.updatePage}
- />
- </div>
+ } else if (this.state.page === 'choose') {
+ signupMethod = (
+ <ChoosePage
+ updatePage={this.updatePage}
+ />
);
- }
-
- if (this.state.page === 'email') {
- return (
- <div>
- {teamListing}
- <EmailSignUpPage/>
- </div>
+ } else if (this.state.page === 'email') {
+ signupMethod = (
+ <EmailSignUpPage/>
);
} else if (this.state.page === 'ldap') {
return (
@@ -148,35 +161,45 @@ export default class TeamSignUp extends React.Component {
</div>
);
} else if (this.state.page === 'gitlab') {
- return (
- <div>
- {teamListing}
- <SSOSignupPage service={Constants.GITLAB_SERVICE}/>
- </div>
+ signupMethod = (
+ <SSOSignupPage service={Constants.GITLAB_SERVICE}/>
);
} else if (this.state.page === 'google') {
- return (
- <div>
- {teamListing}
- <SSOSignupPage service={Constants.GOOGLE_SERVICE}/>
- </div>
+ signupMethod = (
+ <SSOSignupPage service={Constants.GOOGLE_SERVICE}/>
);
} else if (this.state.page === 'none') {
- return (
- <div>
- <FormattedMessage
- id='signup_team.none'
- defaultMessage='No team creation method has been enabled. Please contact an administrator for access.'
- />
- </div>
+ signupMethod = (
+ <FormattedMessage
+ id='signup_team.none'
+ defaultMessage='No team creation method has been enabled. Please contact an administrator for access.'
+ />
);
}
- return null;
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h1>{global.window.mm_config.SiteName}</h1>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='web.root.singup_info'
+ />
+ </h4>
+ <div id='signup-team'>
+ {teamListing}
+ {signupMethod}
+ </div>
+ </div>
+ </div>
+ );
}
}
TeamSignUp.propTypes = {
- teams: React.PropTypes.array
};
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
deleted file mode 100644
index 16553daeb..000000000
--- a/web/react/components/signup_team_complete.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import WelcomePage from './team_signup_welcome_page.jsx';
-import TeamDisplayNamePage from './team_signup_display_name_page.jsx';
-import TeamURLPage from './team_signup_url_page.jsx';
-import SendInivtesPage from './team_signup_send_invites_page.jsx';
-import UsernamePage from './team_signup_username_page.jsx';
-import PasswordPage from './team_signup_password_page.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
-
-import {FormattedMessage} from 'mm-intl';
-
-export default class SignupTeamComplete extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateParent = this.updateParent.bind(this);
-
- var initialState = BrowserStore.getGlobalItem(props.hash);
-
- if (!initialState) {
- initialState = {};
- initialState.wizard = 'welcome';
- initialState.team = {};
- initialState.team.email = this.props.email;
- initialState.team.allowed_domains = '';
- initialState.invites = [];
- initialState.invites.push('');
- initialState.invites.push('');
- initialState.invites.push('');
- initialState.user = {};
- initialState.hash = this.props.hash;
- initialState.data = this.props.data;
- }
-
- this.state = initialState;
- }
- updateParent(state, skipSet) {
- BrowserStore.setGlobalItem(this.props.hash, state);
-
- if (!skipSet) {
- this.setState(state);
- }
- }
- render() {
- if (this.state.wizard === 'welcome') {
- return (
- <WelcomePage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'team_display_name') {
- return (
- <TeamDisplayNamePage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'team_url') {
- return (
- <TeamURLPage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'send_invites') {
- return (
- <SendInivtesPage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'username') {
- return (
- <UsernamePage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'password') {
- return (
- <PasswordPage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- return (
- <div>
- <FormattedMessage
- id='signup_team_complete.completed'
- defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
- />
- </div>
- );
- }
-}
-
-SignupTeamComplete.defaultProps = {
- hash: '',
- email: '',
- data: ''
-};
-SignupTeamComplete.propTypes = {
- hash: React.PropTypes.string,
- email: React.PropTypes.string,
- data: React.PropTypes.string
-};
diff --git a/web/react/components/signup_team_complete/components/signup_team_complete.jsx b/web/react/components/signup_team_complete/components/signup_team_complete.jsx
new file mode 100644
index 000000000..5ad21e941
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/signup_team_complete.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import BrowserStore from '../../../stores/browser_store.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
+import {browserHistory} from 'react-router';
+
+export default class SignupTeamComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateParent = this.updateParent.bind(this);
+ }
+ componentWillMount() {
+ const data = JSON.parse(this.props.location.query.d);
+ this.hash = this.props.location.query.h;
+
+ var initialState = BrowserStore.getGlobalItem(this.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.team = {};
+ initialState.team.email = data.email;
+ initialState.team.allowed_domains = '';
+ initialState.invites = [];
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.user = {};
+ initialState.hash = this.hash;
+ initialState.data = this.props.location.query.d;
+ }
+
+ this.setState(initialState);
+ }
+ componentDidMount() {
+ browserHistory.push('/signup_team_complete/welcome');
+ }
+ updateParent(state, skipSet) {
+ BrowserStore.setGlobalItem(this.hash, state);
+
+ if (!skipSet) {
+ this.setState(state);
+ browserHistory.push('/signup_team_complete/' + state.wizard);
+ }
+ }
+ render() {
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span classNameName='fa fa-chevron-left'/>
+ <FormattedMessage id='web.header.back'/>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <div id='signup-team-complete'>
+ {React.cloneElement(this.props.children, {
+ state: this.state,
+ updateParent: this.updateParent
+ })}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+SignupTeamComplete.defaultProps = {
+};
+SignupTeamComplete.propTypes = {
+ location: React.PropTypes.object,
+ children: React.PropTypes.node
+};
diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx
index f07b50756..280e53ce4 100644
--- a/web/react/components/team_signup_display_name_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx
@@ -1,8 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
+import * as utils from '../../../utils/utils.jsx';
+import * as client from '../../../utils/client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
@@ -133,4 +133,4 @@ TeamSignupDisplayNamePage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupDisplayNamePage); \ No newline at end of file
+export default injectIntl(TeamSignupDisplayNamePage);
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/signup_team_complete/components/team_signup_email_item.jsx
index 790ec2e5d..c87d6ec07 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_email_item.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
+import * as Utils from '../../../utils/utils.jsx';
import {intlShape, injectIntl, defineMessages} from 'mm-intl';
diff --git a/web/react/components/signup_team_complete/components/team_signup_finished.jsx b/web/react/components/signup_team_complete/components/team_signup_finished.jsx
new file mode 100644
index 000000000..fc5f756e7
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_finished.jsx
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+
+export default class FinishedPage extends React.Component {
+ render() {
+ return (
+ <FormattedMessage
+ id='signup_team_complete.completed'
+ defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
+ />
+ );
+ }
+}
diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/signup_team_complete/components/team_signup_password_page.jsx
index 06c04854f..490a11040 100644
--- a/web/react/components/team_signup_password_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_password_page.jsx
@@ -1,12 +1,13 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from '../utils/client.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
-import UserStore from '../stores/user_store.jsx';
-import Constants from '../utils/constants.jsx';
+import * as Client from '../../../utils/client.jsx';
+import BrowserStore from '../../../stores/browser_store.jsx';
+import UserStore from '../../../stores/user_store.jsx';
+import Constants from '../../../utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
const holders = defineMessages({
passwordError: {
@@ -66,11 +67,11 @@ class TeamSignupPasswordPage extends React.Component {
props.state.wizard = 'finished';
props.updateParent(props.state, true);
- window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
+ browserHistory.push('/' + teamSignup.team.name + '/channels/town-square');
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
- window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
+ browserHistory.push('/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name));
} else {
this.setState({serverError: err.message});
$('#finish-button').button('reset');
@@ -211,4 +212,4 @@ TeamSignupPasswordPage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupPasswordPage); \ No newline at end of file
+export default injectIntl(TeamSignupPasswordPage);
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx
index 55cfe5114..5e987ef2c 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import EmailItem from './team_signup_email_item.jsx';
-import * as Client from '../utils/client.jsx';
+import * as Client from '../../../utils/client.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/signup_team_complete/components/team_signup_url_page.jsx
index 2f6c3df49..ec50e2d25 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_url_page.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import Constants from '../utils/constants.jsx';
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import Constants from '../../../utils/constants.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
@@ -202,4 +202,4 @@ TeamSignupUrlPage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupUrlPage); \ No newline at end of file
+export default injectIntl(TeamSignupUrlPage);
diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/signup_team_complete/components/team_signup_username_page.jsx
index 0fa9cb103..e56aa4cd7 100644
--- a/web/react/components/team_signup_username_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_username_page.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import Constants from '../utils/constants.jsx';
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import Constants from '../../../utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -161,4 +161,4 @@ TeamSignupUsernamePage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupUsernamePage); \ No newline at end of file
+export default injectIntl(TeamSignupUsernamePage);
diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx
index 9939c3ffd..97782e54a 100644
--- a/web/react/components/team_signup_welcome_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx
@@ -1,12 +1,14 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import BrowserStore from '../../../stores/browser_store.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
+
const holders = defineMessages({
storageError: {
id: 'team_signup_welcome.storageError',
@@ -73,7 +75,7 @@ class TeamSignupWelcomePage extends React.Component {
} else {
this.props.state.wizard = 'finished';
this.props.updateParent(this.props.state);
- window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(email);
+ browserHistory.push('/signup_team_confirm/?email=' + encodeURIComponent(email));
}
}.bind(this),
function error(err) {
@@ -229,4 +231,4 @@ TeamSignupWelcomePage.propTypes = {
state: React.PropTypes.object
};
-export default injectIntl(TeamSignupWelcomePage); \ No newline at end of file
+export default injectIntl(TeamSignupWelcomePage);
diff --git a/web/react/components/signup_team_confirm.jsx b/web/react/components/signup_team_confirm.jsx
index 290d8e503..1afbb3d30 100644
--- a/web/react/components/signup_team_confirm.jsx
+++ b/web/react/components/signup_team_confirm.jsx
@@ -6,30 +6,41 @@ import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
export default class SignupTeamConfirm extends React.Component {
render() {
return (
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='signup_team_confirm.title'
- defaultMessage='Sign up Complete'
- />
- </h3>
- <p>
- <FormattedHTMLMessage
- id='signup_team_confirm.checkEmail'
- defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team'
- values={{
- email: this.props.email
- }}
- />
- </p>
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div classNameName='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='signup_team_confirm.title'
+ defaultMessage='Sign up Complete'
+ />
+ </h3>
+ <p>
+ <FormattedHTMLMessage
+ id='signup_team_confirm.checkEmail'
+ defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team'
+ values={{
+ email: this.props.location.query.email
+ }}
+ />
+ </p>
+ </div>
+ </div>
</div>
);
}
}
SignupTeamConfirm.defaultProps = {
- email: ''
};
SignupTeamConfirm.propTypes = {
- email: React.PropTypes.string
+ location: React.PropTypes.object
};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index dbec3d02d..d2128a50f 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -2,83 +2,130 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
+import * as Client from '../utils/client.jsx';
import UserStore from '../stores/user_store.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import Constants from '../utils/constants.jsx';
+import LoadingScreen from '../components/loading_screen.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
-
-const holders = defineMessages({
- required: {
- id: 'signup_user_completed.required',
- defaultMessage: 'This field is required'
- },
- validEmail: {
- id: 'signup_user_completed.validEmail',
- defaultMessage: 'Please enter a valid email address'
- },
- reserved: {
- id: 'signup_user_completed.reserved',
- defaultMessage: 'This username is reserved, please choose a new one.'
- },
- usernameLength: {
- id: 'signup_user_completed.usernameLength',
- defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.'
- },
- passwordLength: {
- id: 'signup_user_completed.passwordLength',
- defaultMessage: 'Please enter at least {min} characters'
- }
-});
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
class SignupUserComplete extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
+ this.inviteInfoRecieved = this.inviteInfoRecieved.bind(this);
+
+ this.state = {
+ data: '',
+ hash: '',
+ usedBefore: false,
+ email: '',
+ teamDisplayName: '',
+ teamName: '',
+ teamId: ''
+ };
+ }
+ componentWillMount() {
+ let data = this.props.location.query.d;
+ let hash = this.props.location.query.h;
+ const inviteId = this.props.location.query.id;
+ let usedBefore = false;
+ let email = '';
+ let teamDisplayName = '';
+ let teamName = '';
+ let teamId = '';
+
+ // If we have a hash in the url then we are attempting to access a private team
+ if (hash) {
+ const parsedData = JSON.parse(data);
+ usedBefore = BrowserStore.getGlobalItem(hash);
+ email = parsedData.email;
+ teamDisplayName = parsedData.display_name;
+ teamName = parsedData.name;
+ teamId = parsedData.id;
+ } else {
+ Client.getInviteInfo(this.inviteInfoRecieved, null, inviteId);
+ data = '';
+ hash = '';
+ }
- var initialState = BrowserStore.getGlobalItem(this.props.hash);
-
- if (!initialState) {
- initialState = {};
- initialState.wizard = 'welcome';
- initialState.user = {};
- initialState.user.team_id = this.props.teamId;
- initialState.user.email = this.props.email;
- initialState.original_email = this.props.email;
+ this.setState({
+ data,
+ hash,
+ usedBefore,
+ email,
+ teamDisplayName,
+ teamName,
+ teamId
+ });
+ }
+ inviteInfoRecieved(data) {
+ if (!data) {
+ return;
}
- this.state = initialState;
+ this.setState({
+ teamDisplayName: data.display_name,
+ teamName: data.name,
+ teamId: data.id
+ });
}
handleSubmit(e) {
e.preventDefault();
- const {formatMessage} = this.props.intl;
const providedEmail = ReactDOM.findDOMNode(this.refs.email).value.trim();
if (!providedEmail) {
- this.setState({nameError: '', emailError: formatMessage(holders.required), passwordError: ''});
+ this.setState({
+ nameError: '',
+ emailError: (<FormattedMessage id='signup_user_completed.required'/>),
+ passwordError: '',
+ serverError: ''
+ });
return;
}
if (!Utils.isEmail(providedEmail)) {
- this.setState({nameError: '', emailError: formatMessage(holders.validEmail), passwordError: ''});
+ this.setState({
+ nameError: '',
+ emailError: (<FormattedMessage id='signup_user_completed.validEmail'/>),
+ passwordError: '',
+ serverError: ''
+ });
return;
}
const providedUsername = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
if (!providedUsername) {
- this.setState({nameError: formatMessage(holders.required), emailError: '', passwordError: '', serverError: ''});
+ this.setState({
+ nameError: (<FormattedMessage id='signup_user_completed.required'/>),
+ emailError: '',
+ passwordError: '',
+ serverError: ''
+ });
return;
}
const usernameError = Utils.isValidUsername(providedUsername);
if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({nameError: formatMessage(holders.reserved), emailError: '', passwordError: '', serverError: ''});
+ this.setState({
+ nameError: (<FormattedMessage id='signup_user_completed.reserved'/>),
+ emailError: '',
+ passwordError: '',
+ serverError: ''
+ });
return;
} else if (usernameError) {
this.setState({
- nameError: formatMessage(holders.usernameLength, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}),
+ nameError: (
+ <FormattedMessage
+ id='signup_user_completed.usernameLength'
+ min={Constants.MIN_USERNAME_LENGTH}
+ max={Constants.MAX_USERNAME_LENGTH}
+ />
+ ),
emailError: '',
passwordError: '',
serverError: ''
@@ -88,41 +135,50 @@ class SignupUserComplete extends React.Component {
const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!providedPassword || providedPassword.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({nameError: '', emailError: '', passwordError: formatMessage(holders.passwordLength, {min: Constants.MIN_PASSWORD_LENGTH}), serverError: ''});
+ this.setState({
+ nameError: '',
+ emailError: '',
+ passwordError: (
+ <FormattedMessage
+ id='signup_user_completed.passwordLength'
+ min={Constants.MIN_PASSWORD_LENGTH}
+ />
+ ),
+ serverError: ''
+ });
return;
}
- const user = {
- team_id: this.props.teamId,
- email: providedEmail,
- username: providedUsername,
- password: providedPassword,
- allow_marketing: true
- };
-
this.setState({
- user,
nameError: '',
emailError: '',
passwordError: '',
serverError: ''
});
- client.createUser(user, this.props.data, this.props.hash,
+ const user = {
+ team_id: this.state.teamId,
+ email: providedEmail,
+ username: providedUsername,
+ password: providedPassword,
+ allow_marketing: true
+ };
+
+ Client.createUser(user, this.state.data, this.state.hash,
() => {
- client.track('signup', 'signup_user_02_complete');
+ Client.track('signup', 'signup_user_02_complete');
- client.loginByEmail(this.props.teamName, user.email, user.password,
+ Client.loginByEmail(this.state.teamName, user.email, user.password,
() => {
UserStore.setLastEmail(user.email);
- if (this.props.hash > 0) {
- BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
+ if (this.state.hash > 0) {
+ BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
}
- window.location.href = '/' + this.props.teamName + '/channels/town-square';
+ browserHistory.push('/' + this.state.teamName + '/channels/town-square');
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
- window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
+ browserHistory.push('/should_verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.state.teamName));
} else {
this.setState({serverError: err.message});
}
@@ -135,9 +191,10 @@ class SignupUserComplete extends React.Component {
);
}
render() {
- client.track('signup', 'signup_user_01_welcome');
+ Client.track('signup', 'signup_user_01_welcome');
- if (this.state.wizard === 'finished') {
+ // If we have been used then just display a message
+ if (this.state.usedBefore) {
return (
<div>
<FormattedMessage
@@ -148,6 +205,12 @@ class SignupUserComplete extends React.Component {
);
}
+ // If we haven't got a team id yet we are waiting for
+ // the client so just show the standard loading screen
+ if (this.state.teamId === '') {
+ return (<LoadingScreen/>);
+ }
+
// set up error labels
var emailError = null;
var emailHelpText = (
@@ -160,7 +223,7 @@ class SignupUserComplete extends React.Component {
);
var emailDivStyle = 'form-group';
if (this.state.emailError) {
- emailError = <label className='control-label'>{this.state.emailError}</label>;
+ emailError = (<label className='control-label'>{this.state.emailError}</label>);
emailHelpText = '';
emailDivStyle += ' has-error';
}
@@ -203,13 +266,13 @@ class SignupUserComplete extends React.Component {
// set up the email entry and hide it if an email was provided
var yourEmailIs = '';
- if (this.state.user.email) {
+ if (this.state.email) {
yourEmailIs = (
<FormattedHTMLMessage
id='signup_user_completed.emailIs'
defaultMessage="Your email address is <strong>{email}</strong>. You'll use this address to sign in to {siteName}."
values={{
- email: this.state.user.email,
+ email: this.state.email,
siteName: global.window.mm_config.SiteName
}}
/>
@@ -217,7 +280,7 @@ class SignupUserComplete extends React.Component {
}
var emailContainerStyle = 'margin--extra';
- if (this.state.original_email) {
+ if (this.state.email) {
emailContainerStyle = 'hidden';
}
@@ -234,7 +297,7 @@ class SignupUserComplete extends React.Component {
type='email'
ref='email'
className='form-control'
- defaultValue={this.state.user.email}
+ defaultValue={this.state.email}
placeholder=''
maxLength='128'
autoFocus={true}
@@ -249,20 +312,20 @@ class SignupUserComplete extends React.Component {
var signupMessage = [];
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
signupMessage.push(
- <a
- className='btn btn-custom-login gitlab'
- key='gitlab'
- href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='signup_user_completed.gitlab'
- defaultMessage='with GitLab'
- />
- </span>
- </a>
- );
+ <a
+ className='btn btn-custom-login gitlab'
+ key='gitlab'
+ href={'/api/v1/oauth/gitlab/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
+ >
+ <span className='icon'/>
+ <span>
+ <FormattedMessage
+ id='signup_user_completed.gitlab'
+ defaultMessage='with GitLab'
+ />
+ </span>
+ </a>
+ );
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
@@ -270,7 +333,7 @@ class SignupUserComplete extends React.Component {
<a
className='btn btn-custom-login google'
key='google'
- href={'/' + this.props.teamName + '/signup/google' + window.location.search}
+ href={'/api/v1/oauth/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
>
<span className='icon'/>
<span>
@@ -318,16 +381,16 @@ class SignupUserComplete extends React.Component {
/>
</strong></h5>
<div className={passwordDivStyle}>
- <input
- type='password'
- ref='password'
- className='form-control'
- placeholder=''
- maxLength='128'
- spellCheck='false'
- />
- {passwordError}
- </div>
+ <input
+ type='password'
+ ref='password'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ spellCheck='false'
+ />
+ {passwordError}
+ </div>
</div>
</div>
<p className='margin--extra'>
@@ -373,58 +436,56 @@ class SignupUserComplete extends React.Component {
return (
<div>
- <form>
- <img
- className='signup-team-logo'
- src='/static/images/logo.png'
- />
- <h5 className='margin--less'>
- <FormattedMessage
- id='signup_user_completed.welcome'
- defaultMessage='Welcome to:'
- />
- </h5>
- <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>
- <FormattedMessage
- id='signup_user_completed.onSite'
- defaultMessage='on {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </h2>
- <h4 className='color--light'>
- <FormattedMessage
- id='signup_user_completed.lets'
- defaultMessage="Let's create your account"
- />
- </h4>
- {signupMessage}
- {emailSignup}
- {serverError}
- </form>
+ <div className='signup-header'>
+ <a href='/'>
+ <span classNameNameName='fa fa-chevron-left'/>
+ <FormattedMessage id='web.header.back'/>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container padding--less'>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h5 className='margin--less'>
+ <FormattedMessage
+ id='signup_user_completed.welcome'
+ defaultMessage='Welcome to:'
+ />
+ </h5>
+ <h2 className='signup-team__name'>{this.state.teamName}</h2>
+ <h2 className='signup-team__subdomain'>
+ <FormattedMessage
+ id='signup_user_completed.onSite'
+ defaultMessage='on {siteName}'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h2>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='signup_user_completed.lets'
+ defaultMessage="Let's create your account"
+ />
+ </h4>
+ {signupMessage}
+ {emailSignup}
+ {serverError}
+ </form>
+ </div>
+ </div>
</div>
);
}
}
SignupUserComplete.defaultProps = {
- teamName: '',
- hash: '',
- teamId: '',
- email: '',
- data: null,
- teamDisplayName: ''
};
SignupUserComplete.propTypes = {
- intl: intlShape.isRequired,
- teamName: React.PropTypes.string,
- hash: React.PropTypes.string,
- teamId: React.PropTypes.string,
- email: React.PropTypes.string,
- data: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ location: React.PropTypes.object
};
-export default injectIntl(SignupUserComplete); \ No newline at end of file
+export default SignupUserComplete;
diff --git a/web/react/components/suggestion/at_mention_provider.jsx b/web/react/components/suggestion/at_mention_provider.jsx
index 064b75ac5..c5bd13c26 100644
--- a/web/react/components/suggestion/at_mention_provider.jsx
+++ b/web/react/components/suggestion/at_mention_provider.jsx
@@ -40,7 +40,7 @@ class AtMentionSuggestion extends React.Component {
icon = (
<img
className='mention-img'
- src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at}
/>
);
}
diff --git a/web/react/components/suggestion/suggestion_box.jsx b/web/react/components/suggestion/suggestion_box.jsx
index ea9f835eb..12b098cbd 100644
--- a/web/react/components/suggestion/suggestion_box.jsx
+++ b/web/react/components/suggestion/suggestion_box.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import Constants from '../../utils/constants.jsx';
-import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import SuggestionStore from '../../stores/suggestion_store.jsx';
import * as Utils from '../../utils/utils.jsx';
@@ -48,7 +48,7 @@ export default class SuggestionBox extends React.Component {
if (!(container.is(e.target) || container.has(e.target).length > 0)) {
// we can't just use blur for this because it fires and hides the children before
// their click handlers can be called
- EventHelpers.emitClearSuggestions(this.suggestionId);
+ GlobalActions.emitClearSuggestions(this.suggestionId);
}
}
@@ -57,7 +57,7 @@ export default class SuggestionBox extends React.Component {
const caret = Utils.getCaretPosition(textbox);
const pretext = textbox.value.substring(0, caret);
- EventHelpers.emitSuggestionPretextChanged(this.suggestionId, pretext);
+ GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
if (this.props.onUserInput) {
this.props.onUserInput(textbox.value);
@@ -89,13 +89,13 @@ export default class SuggestionBox extends React.Component {
handleKeyDown(e) {
if (SuggestionStore.hasSuggestions(this.suggestionId)) {
if (e.which === KeyCodes.UP) {
- EventHelpers.emitSelectPreviousSuggestion(this.suggestionId);
+ GlobalActions.emitSelectPreviousSuggestion(this.suggestionId);
e.preventDefault();
} else if (e.which === KeyCodes.DOWN) {
- EventHelpers.emitSelectNextSuggestion(this.suggestionId);
+ GlobalActions.emitSelectNextSuggestion(this.suggestionId);
e.preventDefault();
} else if (e.which === KeyCodes.ENTER || e.which === KeyCodes.TAB) {
- EventHelpers.emitCompleteWordSuggestion(this.suggestionId);
+ GlobalActions.emitCompleteWordSuggestion(this.suggestionId);
e.preventDefault();
} else if (this.props.onKeyDown) {
this.props.onKeyDown(e);
diff --git a/web/react/components/suggestion/suggestion_list.jsx b/web/react/components/suggestion/suggestion_list.jsx
index e3ccd0f08..ccebeb990 100644
--- a/web/react/components/suggestion/suggestion_list.jsx
+++ b/web/react/components/suggestion/suggestion_list.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import SuggestionStore from '../../stores/suggestion_store.jsx';
export default class SuggestionList extends React.Component {
@@ -36,7 +36,7 @@ export default class SuggestionList extends React.Component {
}
handleItemClick(term, e) {
- EventHelpers.emitCompleteWordSuggestion(this.props.suggestionId, term);
+ GlobalActions.emitCompleteWordSuggestion(this.props.suggestionId, term);
e.preventDefault();
}
diff --git a/web/react/components/team_members_modal.jsx b/web/react/components/team_members_modal.jsx
index 9bdb16438..786e8f947 100644
--- a/web/react/components/team_members_modal.jsx
+++ b/web/react/components/team_members_modal.jsx
@@ -10,8 +10,36 @@ import {FormattedMessage} from 'mm-intl';
const Modal = ReactBootstrap.Modal;
export default class TeamMembersModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.teamChanged = this.teamChanged.bind(this);
+
+ this.state = {
+ team: TeamStore.getCurrent()
+ };
+ }
+ componentDidMount() {
+ if (this.props.show) {
+ this.onShow();
+ }
+
+ TeamStore.addChangeListener(this.teamChanged);
+ }
+
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.teamChanged);
+ }
+
+ teamChanged() {
+ this.setState({team: TeamStore.getCurrent()});
+ }
+
render() {
- const team = TeamStore.getCurrent();
+ let teamDisplayName = '';
+ if (this.state.team) {
+ teamDisplayName = this.state.team.display_name;
+ }
let maxHeight = 1000;
if (Utils.windowHeight() <= 1200) {
@@ -29,7 +57,7 @@ export default class TeamMembersModal extends React.Component {
id='team_member_modal.members'
defaultMessage='{team} Members'
values={{
- team: team.display_name
+ team: teamDisplayName
}}
/>
</Modal.Header>
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index e3207d573..0eb9d1211 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -28,6 +28,9 @@ export default class TeamSettings extends React.Component {
}
}
render() {
+ if (!this.state.team) {
+ return null;
+ }
var result;
switch (this.props.activeTab) {
case 'general':
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index 7dd645b25..a81b22d90 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -5,6 +5,7 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
const holders = defineMessages({
emailError: {
@@ -47,9 +48,9 @@ class EmailSignUpPage extends React.Component {
Client.signupTeam(team.email,
(data) => {
if (data.follow_link) {
- window.location.href = data.follow_link;
+ browserHistory.push(data.follow_link);
} else {
- window.location.href = `/signup_team_confirm/?email=${encodeURIComponent(team.email)}`;
+ browserHistory.push(`/signup_team_confirm/?email=${encodeURIComponent(team.email)}`);
}
},
(err) => {
@@ -117,4 +118,4 @@ EmailSignUpPage.propTypes = {
intl: intlShape.isRequired
};
-export default injectIntl(EmailSignUpPage); \ No newline at end of file
+export default injectIntl(EmailSignUpPage);
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 46900e436..d4eb60676 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -61,7 +61,7 @@ export default class Textbox extends React.Component {
onRecievedError() {
const errorCount = ErrorStore.getConnectionErrorCount();
- if (errorCount > 0) {
+ if (errorCount > 1) {
this.setState({connection: 'bad-connection'});
} else {
this.setState({connection: ''});
@@ -194,7 +194,6 @@ export default class Textbox extends React.Component {
defaultMessage='>quote'
/>
</span>
- {previewLink}
</div>
);
@@ -230,16 +229,19 @@ export default class Textbox extends React.Component {
>
</div>
{helpText}
- <a
- target='_blank'
- href='http://docs.mattermost.com/help/getting-started/messaging-basics.html'
- className='textbox-help-link'
- >
- <FormattedMessage
- id='textbox.help'
- defaultMessage='Help'
- />
- </a>
+ <div className='help__text'>
+ {previewLink}
+ <a
+ target='_blank'
+ href='http://docs.mattermost.com/help/getting-started/messaging-basics.html'
+ className='textbox-help-link'
+ >
+ <FormattedMessage
+ id='textbox.help'
+ defaultMessage='Help'
+ />
+ </a>
+ </div>
</div>
);
}
diff --git a/web/react/components/user_list_row.jsx b/web/react/components/user_list_row.jsx
index d8442e770..1ca40687f 100644
--- a/web/react/components/user_list_row.jsx
+++ b/web/react/components/user_list_row.jsx
@@ -32,7 +32,7 @@ export default function UserListRow({user, actions}) {
>
<img
className='profile-img'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
/>
<div
className='user-list-item__details'
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 31b2b9907..e7a286b77 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -26,22 +26,27 @@ export default class UserProfile extends React.Component {
}
}
render() {
- var name = Utils.displayUsername(this.props.user.id);
- if (this.props.overwriteName) {
- name = this.props.overwriteName;
- } else if (!name) {
- name = '...';
+ let name = '...';
+ let email = '';
+ let profileImg = '';
+ if (this.props.user) {
+ name = Utils.displayUsername(this.props.user.id);
+ email = this.props.user.email;
+ profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at;
}
- if (this.props.disablePopover) {
- return <div>{name}</div>;
+ if (this.props.overwriteName) {
+ name = this.props.overwriteName;
}
- var profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at + '&' + Utils.getSessionIndex();
if (this.props.overwriteImage) {
profileImg = this.props.overwriteImage;
}
+ if (this.props.disablePopover) {
+ return <div>{name}</div>;
+ }
+
var dataContent = [];
dataContent.push(
<img
@@ -69,14 +74,14 @@ export default class UserProfile extends React.Component {
dataContent.push(
<div
data-toggle='tooltip'
- title={this.props.user.email}
+ title={email}
key='user-popover-email'
>
<a
- href={'mailto:' + this.props.user.email}
+ href={'mailto:' + email}
className='text-nowrap text-lowercase user-popover__email'
>
- {this.props.user.email}
+ {email}
</a>
</div>
);
@@ -114,7 +119,7 @@ UserProfile.defaultProps = {
disablePopover: false
};
UserProfile.propTypes = {
- user: React.PropTypes.object.isRequired,
+ user: React.PropTypes.object,
overwriteName: React.PropTypes.string,
overwriteImage: React.PropTypes.string,
disablePopover: React.PropTypes.bool
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index 1e724bb6e..4ee9fd0e2 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -253,6 +253,8 @@ class CustomThemeChooser extends React.Component {
</div>
</div>
);
+
+ colors += theme[element.id] + ',';
} else if (element.group === 'sidebarElements') {
sidebarElements.push(
<div
diff --git a/web/react/components/user_settings/manage_languages.jsx b/web/react/components/user_settings/manage_languages.jsx
index 2d1c74717..6b00a65c7 100644
--- a/web/react/components/user_settings/manage_languages.jsx
+++ b/web/react/components/user_settings/manage_languages.jsx
@@ -5,6 +5,7 @@ import SettingItemMax from '../setting_item_max.jsx';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -41,7 +42,7 @@ export default class ManageLanguage extends React.Component {
submitUser(user) {
Client.updateUser(user,
() => {
- window.location.reload(true);
+ GlobalActions.newLocalizationSelected(user.locale);
},
(err) => {
let serverError;
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index 0acfd4a16..1dd564c8d 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -3,7 +3,7 @@
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
-import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -28,7 +28,7 @@ class DeveloperTab extends React.Component {
}
register() {
this.props.closeModal();
- EventHelpers.showRegisterAppModal();
+ GlobalActions.showRegisterAppModal();
}
render() {
var appSection;
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index b0b1c414e..235892819 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -13,7 +13,7 @@ import Constants from '../../utils/constants.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
const holders = defineMessages({
usernameReserved: {
@@ -712,7 +712,7 @@ class UserSettingsGeneralTab extends React.Component {
<SettingPicture
title={formatMessage(holders.profilePicture)}
submit={this.submitPicture}
- src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
server_error={serverError}
client_error={clientError}
updateSection={(e) => {
@@ -729,7 +729,14 @@ class UserSettingsGeneralTab extends React.Component {
let minMessage = formatMessage(holders.uploadImage);
if (user.last_picture_update) {
minMessage = formatMessage(holders.imageUpdated, {
- date: new Date(user.last_picture_update).toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'})
+ date: (
+ <FormattedDate
+ value={new Date(user.last_picture_update)}
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ )
});
}
pictureSection = (
@@ -805,4 +812,4 @@ UserSettingsGeneralTab.propTypes = {
collapseModal: React.PropTypes.func.isRequired
};
-export default injectIntl(UserSettingsGeneralTab); \ No newline at end of file
+export default injectIntl(UserSettingsGeneralTab);
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index fa3415988..0c4a3d526 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -73,27 +73,35 @@ class UserSettingsModal extends React.Component {
this.updateTab = this.updateTab.bind(this);
this.updateSection = this.updateSection.bind(this);
+ this.onUserChanged = this.onUserChanged.bind(this);
this.state = {
active_tab: 'general',
active_section: '',
showConfirmModal: false,
- enforceFocus: true
+ enforceFocus: true,
+ currentUser: UserStore.getCurrentUser()
};
this.requireConfirm = false;
}
+ onUserChanged() {
+ this.setState({currentUser: UserStore.getCurrentUser()});
+ }
+
componentDidMount() {
if (this.props.show) {
this.handleShow();
}
+ UserStore.addChangeListener(this.onUserChanged);
}
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
this.handleShow();
}
+ UserStore.removeChangeListener(this.onUserChanged);
}
handleShow() {
@@ -235,8 +243,10 @@ class UserSettingsModal extends React.Component {
render() {
const {formatMessage} = this.props.intl;
- var currentUser = UserStore.getCurrentUser();
- var isAdmin = Utils.isAdmin(currentUser.roles);
+ if (this.state.currentUser == null) {
+ return (<div/>);
+ }
+ var isAdmin = Utils.isAdmin(this.state.currentUser.roles);
var tabs = [];
tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'});
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index cba7ffdea..0b6b6c398 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -14,7 +14,7 @@ import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
import Constants from '../../utils/constants.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedTime, FormattedDate} from 'mm-intl';
const holders = defineMessages({
currentPasswordError: {
@@ -218,11 +218,24 @@ class SecurityTab extends React.Component {
var describe;
var d = new Date(this.props.user.last_password_update);
- const locale = global.window.mm_locale;
const hours12 = !Utils.isMilitaryTime();
describe = formatMessage(holders.lastUpdated, {
- date: d.toLocaleDateString(locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: d.toLocaleTimeString(locale, {hour12: hours12, hour: '2-digit', minute: '2-digit'})
+ date: (
+ <FormattedDate
+ value={d}
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ ),
+ time: (
+ <FormattedTime
+ value={d}
+ hour12={hours12}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ )
});
updateSectionStatus = function updateSection() {
@@ -251,7 +264,7 @@ class SecurityTab extends React.Component {
<div>
<a
className='btn btn-primary'
- href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email)}
+ href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service}
>
<FormattedMessage
id='user.settings.security.switchEmail'
@@ -269,7 +282,7 @@ class SecurityTab extends React.Component {
<div>
<a
className='btn btn-primary'
- href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GITLAB_SERVICE}
+ href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchGitlab'
@@ -287,7 +300,7 @@ class SecurityTab extends React.Component {
<div>
<a
className='btn btn-primary'
- href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GOOGLE_SERVICE}
+ href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GOOGLE_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchGoogle'
@@ -456,4 +469,4 @@ SecurityTab.propTypes = {
setEnforceFocus: React.PropTypes.func.isRequired
};
-export default injectIntl(SecurityTab); \ No newline at end of file
+export default injectIntl(SecurityTab);
diff --git a/web/react/components/view_image_popover_bar.jsx b/web/react/components/view_image_popover_bar.jsx
index 819df76d8..18be5a3c5 100644
--- a/web/react/components/view_image_popover_bar.jsx
+++ b/web/react/components/view_image_popover_bar.jsx
@@ -51,6 +51,7 @@ export default class ViewImagePopoverBar extends React.Component {
href={this.props.fileURL}
download={this.props.filename}
className='text'
+ target='_blank'
>
<FormattedMessage
id='view_image_popover.download'
diff --git a/web/react/package.json b/web/react/package.json
index 07ffa0cdf..509c9967b 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -11,6 +11,8 @@
"marked": "mattermost/marked#cb85e5cc81bc7937dbb73c3c53d9532b1b97e3ca",
"mm-intl": "mattermost/mm-intl#805442fd474fa40cd586ddeda404dbbe8e60626d",
"object-assign": "4.0.1",
+ "react": "0.14.3",
+ "react-router": "2.0.0",
"twemoji": "1.4.1"
},
"devDependencies": {
diff --git a/web/react/pages/admin_console.jsx b/web/react/pages/admin_console.jsx
deleted file mode 100644
index 989936d9e..000000000
--- a/web/react/pages/admin_console.jsx
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ErrorBar from '../components/error_bar.jsx';
-import SelectTeamModal from '../components/admin_console/select_team_modal.jsx';
-import AdminController from '../components/admin_console/admin_controller.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <div>
- <ErrorBar/>
- <AdminController
- tab={this.props.map.ActiveTab}
- teamId={this.props.map.TeamId}
- />
- <SelectTeamModal/>
- </div>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_admin_console_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('admin_controller')
- );
-};
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
deleted file mode 100644
index bc78c049c..000000000
--- a/web/react/pages/channel.jsx
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ChannelView from '../components/channel_view.jsx';
-import ChannelLoader from '../components/channel_loader.jsx';
-import ErrorBar from '../components/error_bar.jsx';
-import * as Client from '../utils/client.jsx';
-
-import GetPostLinkModal from '../components/get_post_link_modal.jsx';
-import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx';
-import EditPostModal from '../components/edit_post_modal.jsx';
-import DeletePostModal from '../components/delete_post_modal.jsx';
-import MoreChannelsModal from '../components/more_channels.jsx';
-import TeamSettingsModal from '../components/team_settings_modal.jsx';
-import RemovedFromChannelModal from '../components/removed_from_channel_modal.jsx';
-import RegisterAppModal from '../components/register_app_modal.jsx';
-import ImportThemeModal from '../components/user_settings/import_theme_modal.jsx';
-import InviteMemberModal from '../components/invite_member_modal.jsx';
-
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <div className='channel-view'>
- <ChannelLoader/>
- <ErrorBar/>
- <ChannelView/>
- <GetPostLinkModal/>
- <GetTeamInviteLinkModal/>
- <InviteMemberModal/>
- <ImportThemeModal/>
- <TeamSettingsModal/>
- <MoreChannelsModal/>
- <EditPostModal/>
- <DeletePostModal/>
- <RemovedFromChannelModal/>
- <RegisterAppModal/>
- </div>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_channel_page = function setup(props, team, channel) {
- if (props.PostId === '') {
- EventHelpers.emitChannelClickEvent(channel);
- } else {
- EventHelpers.emitPostFocusEvent(props.PostId);
- }
-
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('channel_view')
- );
-};
diff --git a/web/react/pages/claim_account.jsx b/web/react/pages/claim_account.jsx
deleted file mode 100644
index abbf72ea3..000000000
--- a/web/react/pages/claim_account.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ClaimAccount from '../components/claim/claim_account.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <ClaimAccount
- email={this.props.map.Email}
- currentType={this.props.map.CurrentType}
- newType={this.props.map.NewType}
- teamName={this.props.map.TeamName}
- teamDisplayName={this.props.map.TeamDisplayName}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_claim_account_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('claim')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/docs.jsx b/web/react/pages/docs.jsx
deleted file mode 100644
index 2e47e3e6a..000000000
--- a/web/react/pages/docs.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Docs from '../components/docs.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <Docs site={this.props.map.Site}/>
- </IntlProvider>
- );
- }
-}
-
-global.window.mm_user = global.window.mm_user || {};
-
-global.window.setup_documentation_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('docs')
- );
-};
diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx
deleted file mode 100644
index 93394fcde..000000000
--- a/web/react/pages/find_team.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import FindTeam from '../components/find_team.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <FindTeam/>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_find_team_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('find-team')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx
deleted file mode 100644
index ff81c4994..000000000
--- a/web/react/pages/home.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TeamStore from '../stores/team_store.jsx';
-import Constants from '../utils/constants.jsx';
-
-function setupHomePage() {
- var last = null;
- if (last == null || last.length === 0) {
- window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + Constants.DEFAULT_CHANNEL;
- } else {
- window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + last;
- }
-}
-
-global.window.setup_home_page = setupHomePage;
diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx
deleted file mode 100644
index ec9080945..000000000
--- a/web/react/pages/login.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Client from '../utils/client.jsx';
-import Login from '../components/login.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <Login
- teamDisplayName={this.props.map.TeamDisplayName}
- teamName={this.props.map.TeamName}
- inviteId={this.props.map.InviteId}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_login_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('login')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx
deleted file mode 100644
index 7caff5034..000000000
--- a/web/react/pages/password_reset.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PasswordReset from '../components/password_reset.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <PasswordReset
- isReset={this.props.map.IsReset}
- teamDisplayName={this.props.map.TeamDisplayName}
- teamName={this.props.map.TeamName}
- hash={this.props.map.Hash}
- data={this.props.map.Data}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_password_reset_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('reset')
- );
-};
diff --git a/web/react/pages/root.jsx b/web/react/pages/root.jsx
new file mode 100644
index 000000000..d0b06e32e
--- /dev/null
+++ b/web/react/pages/root.jsx
@@ -0,0 +1,290 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
+import Root from '../components/root.jsx';
+import Login from '../components/login.jsx';
+import LoggedIn from '../components/logged_in.jsx';
+import NotLoggedIn from '../components/not_logged_in.jsx';
+import NeedsTeam from '../components/needs_team.jsx';
+import PasswordResetSendLink from '../components/password_reset_send_link.jsx';
+import PasswordResetForm from '../components/password_reset_form.jsx';
+import ChannelView from '../components/channel_view.jsx';
+import Sidebar from '../components/sidebar.jsx';
+import * as AsyncClient from '../utils/async_client.jsx';
+import PreferenceStore from '../stores/preference_store.jsx';
+import ChannelStore from '../stores/channel_store.jsx';
+import ErrorStore from '../stores/error_store.jsx';
+import BrowserStore from '../stores/browser_store.jsx';
+import SignupTeam from '../components/signup_team.jsx';
+import * as Client from '../utils/client.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import SignupTeamConfirm from '../components/signup_team_confirm.jsx';
+import SignupUserComplete from '../components/signup_user_complete.jsx';
+import ShouldVerifyEmail from '../components/should_verify_email.jsx';
+import DoVerifyEmail from '../components/do_verify_email.jsx';
+import AdminConsole from '../components/admin_console/admin_controller.jsx';
+import ClaimAccount from '../components/claim/claim_account.jsx';
+
+import SignupTeamComplete from '../components/signup_team_complete/components/signup_team_complete.jsx';
+import WelcomePage from '../components/signup_team_complete/components/team_signup_welcome_page.jsx';
+import TeamDisplayNamePage from '../components/signup_team_complete/components/team_signup_display_name_page.jsx';
+import TeamURLPage from '../components/signup_team_complete/components/team_signup_url_page.jsx';
+import SendInivtesPage from '../components/signup_team_complete/components/team_signup_send_invites_page.jsx';
+import UsernamePage from '../components/signup_team_complete/components/team_signup_username_page.jsx';
+import PasswordPage from '../components/signup_team_complete/components/team_signup_password_page.jsx';
+import FinishedPage from '../components/signup_team_complete/components/team_signup_finished.jsx';
+
+// This is for anything that needs to be done for ALL react components.
+// This runs before we start to render anything.
+function preRenderSetup(callwhendone) {
+ const d1 = Client.getClientConfig(
+ (data, textStatus, xhr) => {
+ if (!data) {
+ return;
+ }
+
+ global.window.mm_config = data;
+
+ var serverVersion = xhr.getResponseHeader('X-Version-ID');
+
+ if (serverVersion !== BrowserStore.getLastServerVersion()) {
+ if (!BrowserStore.getLastServerVersion() || BrowserStore.getLastServerVersion() === '') {
+ BrowserStore.setLastServerVersion(serverVersion);
+ } else {
+ BrowserStore.setLastServerVersion(serverVersion);
+ window.location.reload(true);
+ console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
+ }
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getClientConfig');
+ }
+ );
+
+ const d2 = Client.getClientLicenceConfig(
+ (data) => {
+ if (!data) {
+ return;
+ }
+
+ global.window.mm_license = data;
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getClientLicenceConfig');
+ }
+ );
+
+ // Set these here so they don't fail in client.jsx track
+ global.window.analytics = {};
+ global.window.analytics.page = () => {
+ // Do Nothing
+ };
+ global.window.analytics.track = () => {
+ // Do Nothing
+ };
+
+ $.when(d1, d2).done(callwhendone);
+}
+
+function preLoggedIn(nextState, replace, callback) {
+ const d1 = Client.getAllPreferences(
+ (data) => {
+ if (!data) {
+ return;
+ }
+
+ PreferenceStore.setPreferences(data);
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getAllPreferences');
+ }
+ );
+
+ const d2 = AsyncClient.getChannels();
+
+ $.when(d1, d2).done(() => callback());
+}
+
+function onChannelChange(nextState) {
+ const channelName = nextState.params.channel;
+
+ // Make sure we have all the channels
+ AsyncClient.getChannels(true);
+
+ // Get our channel's ID
+ const channel = ChannelStore.getByName(channelName);
+
+ // User clicked channel
+ GlobalActions.emitChannelClickEvent(channel);
+}
+
+function onRootEnter(nextState, replace, callback) {
+ if (nextState.location.pathname === '/') {
+ Client.getMeLoggedIn((data) => {
+ if (!data || data.logged_in === 'false') {
+ replace({pathname: '/signup_team'});
+ callback();
+ } else {
+ replace({pathname: '/' + data.team_name + '/channels/town-square'});
+ callback();
+ }
+ });
+ return;
+ }
+
+ callback();
+}
+
+function onPermalinkEnter(nextState) {
+ const postId = nextState.params.postid;
+
+ GlobalActions.emitPostFocusEvent(postId);
+}
+
+function onLoggedOut(nextState) {
+ const teamName = nextState.params.team;
+ Client.logout(
+ () => {
+ browserHistory.push('/' + teamName + '/login');
+ BrowserStore.signalLogout();
+ BrowserStore.clear();
+ ErrorStore.clearLastError();
+ },
+ () => {
+ browserHistory.push('/' + teamName + '/login');
+ }
+ );
+}
+
+function renderRootComponent() {
+ ReactDOM.render((
+ <Router
+ history={browserHistory}
+ >
+ <Route
+ path='/'
+ component={Root}
+ onEnter={onRootEnter}
+ >
+ <Route
+ component={LoggedIn}
+ onEnter={preLoggedIn}
+ >
+ <Route
+ path=':team/channels/:channel'
+ onEnter={onChannelChange}
+ components={{
+ sidebar: Sidebar,
+ center: ChannelView
+ }}
+ />
+ <Route
+ path=':team/pl/:postid'
+ onEnter={onPermalinkEnter}
+ components={{
+ sidebar: Sidebar,
+ center: ChannelView
+ }}
+ />
+ <Route
+ path=':team/logout'
+ onEnter={onLoggedOut}
+ components={{
+ sidebar: null,
+ center: null
+ }}
+ />
+ <Route
+ path='admin_console'
+ components={{
+ sidebar: null,
+ center: AdminConsole
+ }}
+ />
+ </Route>
+ <Route component={NotLoggedIn}>
+ <Route
+ path='signup_team'
+ component={SignupTeam}
+ />
+ <Route
+ path='signup_team_complete'
+ component={SignupTeamComplete}
+ >
+ <IndexRoute component={FinishedPage}/>
+ <Route
+ path='welcome'
+ component={WelcomePage}
+ />
+ <Route
+ path='team_display_name'
+ component={TeamDisplayNamePage}
+ />
+ <Route
+ path='team_url'
+ component={TeamURLPage}
+ />
+ <Route
+ path='invites'
+ component={SendInivtesPage}
+ />
+ <Route
+ path='username'
+ component={UsernamePage}
+ />
+ <Route
+ path='password'
+ component={PasswordPage}
+ />
+ </Route>
+ <Route
+ path='signup_user_complete'
+ component={SignupUserComplete}
+ />
+ <Route
+ path='signup_team_confirm'
+ component={SignupTeamConfirm}
+ />
+ <Route
+ path='should_verify_email'
+ component={ShouldVerifyEmail}
+ />
+ <Route
+ path='do_verify_email'
+ component={DoVerifyEmail}
+ />
+ <Route
+ path=':team'
+ component={NeedsTeam}
+ >
+ <IndexRedirect to='login'/>
+ <Route
+ path='login'
+ component={Login}
+ />
+ <Route
+ path='claim'
+ component={ClaimAccount}
+ />
+ <Route
+ path='reset_password'
+ component={PasswordResetSendLink}
+ />
+ <Route
+ path='reset_password_complete'
+ component={PasswordResetForm}
+ />
+ </Route>
+ </Route>
+ </Route>
+ </Router>
+ ),
+ document.getElementById('root'));
+}
+
+global.window.setup_root = () => {
+ // Do the pre-render setup and call renderRootComponent when done
+ preRenderSetup(renderRootComponent);
+};
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
deleted file mode 100644
index f276c3ff7..000000000
--- a/web/react/pages/signup_team.jsx
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupTeam from '../components/signup_team.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired,
- teams: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupTeam teams={this.props.teams}/>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_team_page = function setup(props) {
- var teams = [];
-
- for (var prop in props) {
- if (props.hasOwnProperty(prop)) {
- if (prop !== 'Title' && prop !== 'Locale' && prop !== 'Info') {
- teams.push({name: prop, display_name: props[prop]});
- }
- }
- }
-
- ReactDOM.render(
- <Root
- map={props}
- teams={teams}
- />,
- document.getElementById('signup-team')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx
deleted file mode 100644
index 8c237f698..000000000
--- a/web/react/pages/signup_team_complete.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupTeamComplete from '../components/signup_team_complete.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupTeamComplete
- email={this.props.map.Email}
- hash={this.props.map.Hash}
- data={this.props.map.Data}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_team_complete_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('signup-team-complete')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/signup_team_confirm.jsx b/web/react/pages/signup_team_confirm.jsx
deleted file mode 100644
index 13c8f3fd0..000000000
--- a/web/react/pages/signup_team_confirm.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupTeamConfirm from '../components/signup_team_confirm.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupTeamConfirm
- email={this.props.map.Email}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_team_confirm_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('signup-team-confirm')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx
deleted file mode 100644
index a14f2140b..000000000
--- a/web/react/pages/signup_user_complete.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupUserComplete from '../components/signup_user_complete.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupUserComplete
- teamId={this.props.map.TeamId}
- teamName={this.props.map.TeamName}
- teamDisplayName={this.props.map.TeamDisplayName}
- email={this.props.map.Email}
- hash={this.props.map.Hash}
- data={this.props.map.Data}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_user_complete_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('signup-user-complete')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx
deleted file mode 100644
index 6b336daa1..000000000
--- a/web/react/pages/verify.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import EmailVerify from '../components/email_verify.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <EmailVerify
- isVerified={this.props.map.IsVerified}
- teamURL={this.props.map.TeamURL}
- userEmail={this.props.map.UserEmail}
- resendSuccess={this.props.map.ResendSuccess}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setupVerifyPage = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('verify')
- );
-};
diff --git a/web/react/stores/admin_store.jsx b/web/react/stores/admin_store.jsx
index eb3254cfe..9f7f6e7ff 100644
--- a/web/react/stores/admin_store.jsx
+++ b/web/react/stores/admin_store.jsx
@@ -121,7 +121,11 @@ class AdminStoreClass extends EventEmitter {
}
getSelectedTeams() {
- return BrowserStore.getItem('seleted_teams');
+ const result = BrowserStore.getItem('seleted_teams');
+ if (!result) {
+ return {};
+ }
+ return result;
}
saveSelectedTeams(teams) {
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index 3417faaaf..3b35916b3 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -4,8 +4,8 @@
import {generateId} from '../utils/utils.jsx';
function getPrefix() {
- if (global.window.mm_user) {
- return global.window.mm_user.id + '_';
+ if (global.window.mm_current_user_id) {
+ return global.window.mm_current_user_id + '_';
}
return 'unknown_';
@@ -31,7 +31,9 @@ class BrowserStoreClass {
this.isSignallingLogout = this.isSignallingLogout.bind(this);
this.signalLogin = this.signalLogin.bind(this);
this.isSignallingLogin = this.isSignallingLogin.bind(this);
+ }
+ checkVersion() {
var currentVersion = sessionStorage.getItem('storage_version');
if (currentVersion !== global.window.mm_config.Version) {
sessionStorage.clear();
diff --git a/web/react/stores/localization_store.jsx b/web/react/stores/localization_store.jsx
new file mode 100644
index 000000000..0e3a63724
--- /dev/null
+++ b/web/react/stores/localization_store.jsx
@@ -0,0 +1,60 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
+import EventEmitter from 'events';
+import Constants from '../utils/constants.jsx';
+const ActionTypes = Constants.ActionTypes;
+
+const CHANGE_EVENT = 'change';
+
+class LocalizationStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.currentLocale = 'en';
+ this.currentTranslations = null;
+ }
+
+ emitChange() {
+ this.emit(CHANGE_EVENT);
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+
+ setCurrentLocale(locale, translations) {
+ this.currentLocale = locale;
+ this.currentTranslations = translations;
+ }
+
+ getLocale() {
+ return this.currentLocale;
+ }
+
+ getTranslations() {
+ return this.currentTranslations;
+ }
+}
+
+var LocalizationStore = new LocalizationStoreClass();
+LocalizationStore.setMaxListeners(0);
+
+LocalizationStore.dispatchToken = AppDispatcher.register((payload) => {
+ var action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECEIVED_LOCALE:
+ LocalizationStore.setCurrentLocale(action.locale, action.translations);
+ LocalizationStore.emitChange();
+ break;
+ default:
+ }
+});
+
+export default LocalizationStore;
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index 9b2b049b7..181de53d7 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -10,7 +10,7 @@ import EventEmitter from 'events';
import * as Utils from '../utils/utils.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
const SocketEvents = Constants.SocketEvents;
@@ -31,6 +31,7 @@ class SocketStoreClass extends EventEmitter {
this.close = this.close.bind(this);
this.failCount = 0;
+ this.isInitialize = false;
this.translations = this.getDefaultTranslations();
@@ -42,10 +43,6 @@ class SocketStoreClass extends EventEmitter {
return;
}
- if (!global.window.hasOwnProperty('mm_session_token_index')) {
- return;
- }
-
this.setMaxListeners(0);
if (window.WebSocket && !conn) {
@@ -54,28 +51,27 @@ class SocketStoreClass extends EventEmitter {
protocol = 'wss://';
}
- var connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + '/api/v1/websocket?' + Utils.getSessionIndex();
+ var connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + '/api/v1/websocket';
if (this.failCount === 0) {
console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
- if (ErrorStore.getConnectionErrorCount() > 0) {
- ErrorStore.setConnectionErrorCount(0);
- ErrorStore.emitChange();
- }
}
+
conn = new WebSocket(connUrl);
conn.onopen = () => {
if (this.failCount > 0) {
console.log('websocket re-established connection'); //eslint-disable-line no-console
+ AsyncClient.getChannels();
+ AsyncClient.getPosts(ChannelStore.getCurrentId());
+ }
+ if (this.isInitialize) {
ErrorStore.clearLastError();
ErrorStore.emitChange();
-
- AsyncClient.getChannels();
- AsyncClient.getPosts(ChannelStore.getCurrentId());
}
+ this.isInitialize = true;
this.failCount = 0;
};
@@ -204,7 +200,7 @@ class SocketStoreClass extends EventEmitter {
function handleNewPostEvent(msg, translations) {
// Store post
const post = JSON.parse(msg.props.post);
- EventHelpers.emitPostRecievedEvent(post);
+ GlobalActions.emitPostRecievedEvent(post);
// Update channel state
if (ChannelStore.getCurrentId() === msg.channel_id) {
@@ -291,7 +287,7 @@ function handlePostEditEvent(msg) {
function handlePostDeleteEvent(msg) {
const post = JSON.parse(msg.props.post);
- EventHelpers.emitPostDeletedEvent(post);
+ GlobalActions.emitPostDeletedEvent(post);
}
function handleNewUserEvent() {
@@ -337,7 +333,7 @@ function handleChannelViewedEvent(msg) {
function handlePreferenceChangedEvent(msg) {
const preference = JSON.parse(msg.props.preference);
- EventHelpers.emitPreferenceChangedEvent(preference);
+ GlobalActions.emitPreferenceChangedEvent(preference);
}
var SocketStore = new SocketStoreClass();
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index 7a1a2ef42..354a07b72 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -6,7 +6,6 @@ import EventEmitter from 'events';
import Constants from '../utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
-import BrowserStore from '../stores/browser_store.jsx';
const CHANGE_EVENT = 'change';
@@ -33,6 +32,9 @@ class TeamStoreClass extends EventEmitter {
this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this);
this.getCurrentInviteLink = this.getCurrentInviteLink.bind(this);
this.saveTeam = this.saveTeam.bind(this);
+
+ this.teams = {};
+ this.currentTeamId = '';
}
emitChange() {
@@ -65,11 +67,11 @@ class TeamStoreClass extends EventEmitter {
}
getAll() {
- return BrowserStore.getItem('user_teams', {});
+ return this.teams;
}
getCurrentId() {
- var team = global.window.mm_team;
+ var team = this.get(this.currentTeamId);
if (team) {
return team.id;
@@ -79,11 +81,13 @@ class TeamStoreClass extends EventEmitter {
}
getCurrent() {
- if (global.window.mm_team != null && this.get(global.window.mm_team.id) == null) {
- this.saveTeam(global.window.mm_team);
+ const team = this.teams[this.currentTeamId];
+
+ if (team) {
+ return team;
}
- return global.window.mm_team;
+ return null;
}
getCurrentTeamUrl() {
@@ -104,9 +108,16 @@ class TeamStoreClass extends EventEmitter {
}
saveTeam(team) {
- var teams = this.getAll();
- teams[team.id] = team;
- BrowserStore.setItem('user_teams', teams);
+ this.teams[team.id] = team;
+ }
+
+ saveTeams(teams) {
+ this.teams = teams;
+ }
+
+ saveMyTeam(team) {
+ this.saveTeam(team);
+ this.currentTeamId = team.id;
}
}
@@ -116,11 +127,14 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECEIVED_TEAM:
- TeamStore.saveTeam(action.team);
+ case ActionTypes.RECEIVED_MY_TEAM:
+ TeamStore.saveMyTeam(action.team);
+ TeamStore.emitChange();
+ break;
+ case ActionTypes.RECEIVED_ALL_TEAMS:
+ TeamStore.saveTeams(action.teams);
TeamStore.emitChange();
break;
-
default:
}
});
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 75a87d424..c1e5c75dc 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -11,13 +11,13 @@ import BrowserStore from './browser_store.jsx';
const CHANGE_EVENT = 'change';
const CHANGE_EVENT_SESSIONS = 'change_sessions';
const CHANGE_EVENT_AUDITS = 'change_audits';
-const CHANGE_EVENT_TEAMS = 'change_teams';
const CHANGE_EVENT_STATUSES = 'change_statuses';
class UserStoreClass extends EventEmitter {
constructor() {
super();
this.profileCache = null;
+ this.currentUserId = '';
}
emitChange(userId) {
@@ -56,18 +56,6 @@ class UserStoreClass extends EventEmitter {
this.removeListener(CHANGE_EVENT_AUDITS, callback);
}
- emitTeamsChange() {
- this.emit(CHANGE_EVENT_TEAMS);
- }
-
- addTeamsChangeListener(callback) {
- this.on(CHANGE_EVENT_TEAMS, callback);
- }
-
- removeTeamsChangeListener(callback) {
- this.removeListener(CHANGE_EVENT_TEAMS, callback);
- }
-
emitStatusesChange() {
this.emit(CHANGE_EVENT_STATUSES);
}
@@ -81,26 +69,17 @@ class UserStoreClass extends EventEmitter {
}
getCurrentUser() {
- if (this.getProfiles()[global.window.mm_user.id] == null) {
- this.saveProfile(global.window.mm_user);
- }
-
- return global.window.mm_user;
+ return this.getProfiles()[this.currentUserId];
}
setCurrentUser(user) {
- var oldUser = global.window.mm_user;
-
- if (oldUser.id === user.id) {
- global.window.mm_user = user;
- this.saveProfile(user);
- } else {
- throw new Error('Problem with setCurrentUser old_user_id=' + oldUser.id + ' new_user_id=' + user.id);
- }
+ this.saveProfile(user);
+ this.currentUserId = user.id;
+ global.window.mm_current_user_id = this.currentUserId;
}
getCurrentId() {
- var user = global.window.mm_user;
+ var user = this.getCurrentUser();
if (user) {
return user.id;
@@ -200,11 +179,22 @@ class UserStoreClass extends EventEmitter {
saveProfiles(profiles) {
const currentId = this.getCurrentId();
- if (currentId in profiles) {
- delete profiles[currentId];
+ if (this.profileCache) {
+ const currentUser = this.profileCache[currentId];
+ if (currentUser) {
+ if (currentId in profiles) {
+ delete profiles[currentId];
+ }
+
+ this.profileCache = profiles;
+ this.profileCache[currentId] = currentUser;
+ } else {
+ this.profileCache = profiles;
+ }
+ } else {
+ this.profileCache = profiles;
}
- this.profileCache = profiles;
BrowserStore.setItem('profiles', profiles);
}
@@ -224,14 +214,6 @@ class UserStoreClass extends EventEmitter {
return BrowserStore.getItem('audits', {loading: true});
}
- setTeams(teams) {
- BrowserStore.setItem('teams', teams);
- }
-
- getTeams() {
- return BrowserStore.getItem('teams', []);
- }
-
getCurrentMentionKeys() {
return this.getMentionKeys(this.getCurrentId());
}
@@ -312,10 +294,6 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
UserStore.setAudits(action.audits);
UserStore.emitAuditsChange();
break;
- case ActionTypes.RECEIVED_TEAMS:
- UserStore.setTeams(action.teams);
- UserStore.emitTeamsChange();
- break;
case ActionTypes.RECEIVED_STATUSES:
UserStore.pSetStatuses(action.statuses);
UserStore.emitStatusesChange();
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 7d5e1bd0f..b9770a6e9 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
import * as client from './client.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -44,15 +45,19 @@ function isCallInProgress(callName) {
export function getChannels(checkVersion) {
if (isCallInProgress('getChannels')) {
- return;
+ return null;
}
callTracker.getChannels = utils.getTimestamp();
- client.getChannels(
+ return client.getChannels(
(data, textStatus, xhr) => {
callTracker.getChannels = 0;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
if (checkVersion) {
var serverVersion = xhr.getResponseHeader('X-Version-ID');
@@ -67,10 +72,6 @@ export function getChannels(checkVersion) {
}
}
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNELS,
channels: data.channels,
@@ -392,36 +393,6 @@ export function getAllTeams() {
);
}
-export function findTeams(email) {
- if (isCallInProgress('findTeams_' + email)) {
- return;
- }
-
- var user = UserStore.getCurrentUser();
- if (user) {
- callTracker['findTeams_' + email] = utils.getTimestamp();
- client.findTeams(
- user.email,
- function findTeamsSuccess(data, textStatus, xhr) {
- callTracker['findTeams_' + email] = 0;
-
- if (xhr.status === 304 || !data) {
- return;
- }
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_TEAMS,
- teams: data
- });
- },
- function findTeamsFailure(err) {
- callTracker['findTeams_' + email] = 0;
- dispatchError(err, 'findTeams');
- }
- );
- }
-}
-
export function search(terms) {
if (isCallInProgress('search_' + String(terms))) {
return;
@@ -645,11 +616,11 @@ export function getPostsAfter(postId, offset, numPost) {
export function getMe() {
if (isCallInProgress('getMe')) {
- return;
+ return null;
}
callTracker.getMe = utils.getTimestamp();
- client.getMe(
+ return client.getMe(
(data, textStatus, xhr) => {
callTracker.getMe = 0;
@@ -661,6 +632,8 @@ export function getMe() {
type: ActionTypes.RECEIVED_ME,
me: data
});
+
+ GlobalActions.newLocalizationSelected(data.locale);
},
(err) => {
callTracker.getMe = 0;
@@ -706,11 +679,11 @@ export function getStatuses() {
export function getMyTeam() {
if (isCallInProgress('getMyTeam')) {
- return;
+ return null;
}
callTracker.getMyTeam = utils.getTimestamp();
- client.getMyTeam(
+ return client.getMyTeam(
function getMyTeamSuccess(data, textStatus, xhr) {
callTracker.getMyTeam = 0;
@@ -719,7 +692,7 @@ export function getMyTeam() {
}
AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_TEAM,
+ type: ActionTypes.RECEIVED_MY_TEAM,
team: data
});
},
diff --git a/web/react/utils/channel_intro_messages.jsx b/web/react/utils/channel_intro_messages.jsx
index ed94f94b8..94f3f0ce0 100644
--- a/web/react/utils/channel_intro_messages.jsx
+++ b/web/react/utils/channel_intro_messages.jsx
@@ -8,8 +8,7 @@ import ToggleModalButton from '../components/toggle_modal_button.jsx';
import UserProfile from '../components/user_profile.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import Constants from '../utils/constants.jsx';
-import TeamStore from '../stores/team_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import {FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'mm-intl';
@@ -40,7 +39,7 @@ export function createDMIntroMessage(channel) {
<div className='post-profile-img__container channel-intro-img'>
<img
className='post-profile-img'
- src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at}
height='50'
width='50'
/>
@@ -93,37 +92,19 @@ export function createOffTopicIntroMessage(channel) {
}
export function createDefaultIntroMessage(channel) {
- const team = TeamStore.getCurrent();
- let inviteModalLink;
- if (team.type === Constants.INVITE_TEAM) {
- inviteModalLink = (
- <a
- className='intro-links'
- href='#'
- onClick={EventHelpers.showInviteMemberModal}
- >
- <i className='fa fa-user-plus'></i>
- <FormattedMessage
- id='intro_messages.inviteOthers'
- defaultMessage='Invite others to this team'
- />
- </a>
- );
- } else {
- inviteModalLink = (
- <a
- className='intro-links'
- href='#'
- onClick={EventHelpers.showGetTeamInviteLinkModal}
- >
- <i className='fa fa-user-plus'></i>
- <FormattedMessage
- id='intro_messages.inviteOthers'
- defaultMessage='Invite others to this team'
- />
- </a>
- );
- }
+ const inviteModalLink = (
+ <a
+ className='intro-links'
+ href='#'
+ onClick={GlobalActions.showGetTeamInviteLinkModal}
+ >
+ <i className='fa fa-user-plus'></i>
+ <FormattedMessage
+ id='intro_messages.inviteOthers'
+ defaultMessage='Invite others to this team'
+ />
+ </a>
+ );
return (
<div className='channel-intro'>
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 76d42137a..e00f28a14 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1,8 +1,8 @@
// See License.txt for license information.
import BrowserStore from '../stores/browser_store.jsx';
-import TeamStore from '../stores/team_store.jsx';
-import ErrorStore from '../stores/error_store.jsx';
+
+import {browserHistory} from 'react-router';
let translations = {
connectionError: 'There appears to be a problem with your internet connection.',
@@ -50,10 +50,10 @@ function handleError(methodName, xhr, status, err) {
if (xhr.status === 401) {
if (window.location.href.indexOf('/channels') === 0) {
- window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
+ browserHistory.push('/login?extra=expired&redirect=' + encodeURIComponent(window.location.pathname + window.location.search));
} else {
- var teamURL = window.location.href.split('/channels')[0];
- window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
+ var teamURL = window.location.pathname.split('/channels')[0];
+ browserHistory.push(teamURL + '/login?extra=expired&redirect=' + encodeURIComponent(window.location.pathname + window.location.search));
}
}
@@ -289,13 +289,17 @@ export function switchToEmail(data, success, error) {
track('api', 'api_users_switch_to_email');
}
-export function logout() {
+export function logout(success, error) {
track('api', 'api_users_logout');
- var currentTeamUrl = TeamStore.getCurrentTeamUrl();
- BrowserStore.signalLogout();
- BrowserStore.clear();
- ErrorStore.clearLastError();
- window.location.href = currentTeamUrl + '/logout';
+ $.ajax({
+ url: '/api/v1/users/logout',
+ type: 'POST',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('logout', xhr, status, err);
+ error(e);
+ }
+ });
}
export function loginByEmail(name, email, password, success, error) {
@@ -437,7 +441,7 @@ export function getServerAudits(success, error) {
}
export function getConfig(success, error) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/admin/config',
dataType: 'json',
contentType: 'application/json',
@@ -457,7 +461,6 @@ export function getAnalytics(name, teamId, success, error) {
} else {
url += teamId + '/' + name;
}
-
$.ajax({
url,
dataType: 'json',
@@ -471,6 +474,34 @@ export function getAnalytics(name, teamId, success, error) {
});
}
+export function getClientConfig(success, error) {
+ return $.ajax({
+ url: '/api/v1/admin/client_props',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getClientConfig', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
+export function getTeamAnalytics(teamId, name, success, error) {
+ $.ajax({
+ url: '/api/v1/admin/analytics/' + teamId + '/' + name,
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: (xhr, status, err) => {
+ var e = handleError('getTeamAnalytics', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function saveConfig(config, success, error) {
$.ajax({
url: '/api/v1/admin/save_config',
@@ -529,6 +560,21 @@ export function getAllTeams(success, error) {
});
}
+export function getMeLoggedIn(success, error) {
+ return $.ajax({
+ cache: false,
+ url: '/api/v1/users/me_logged_in',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getMeLoggedIn', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function getMe(success, error) {
var currentUser = null;
$.ajax({
@@ -635,38 +681,6 @@ export function findTeamByName(teamName, success, error) {
});
}
-export function findTeamsSendEmail(email, success, error) {
- $.ajax({
- url: '/api/v1/teams/email_teams',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({email: email}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('findTeamsSendEmail', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_teams_email_teams');
-}
-
-export function findTeams(email, success, error) {
- $.ajax({
- url: '/api/v1/teams/find_teams',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({email: email}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('findTeams', xhr, status, err);
- error(e);
- }
- });
-}
-
export function createChannel(channel, success, error) {
$.ajax({
url: '/api/v1/channels/create',
@@ -835,7 +849,7 @@ export function updateLastViewedAt(channelId, success, error) {
}
export function getChannels(success, error) {
- $.ajax({
+ return $.ajax({
cache: false,
url: '/api/v1/channels/',
dataType: 'json',
@@ -901,7 +915,7 @@ export function getChannelExtraInfo(id, memberLimit, success, error) {
url += '/' + memberLimit;
}
- $.ajax({
+ return $.ajax({
url,
dataType: 'json',
contentType: 'application/json',
@@ -1018,7 +1032,7 @@ export function getPostsPage(channelId, offset, limit, success, error, complete)
}
export function getPosts(channelId, since, success, error, complete) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/channels/' + channelId + '/posts/' + since,
dataType: 'json',
type: 'GET',
@@ -1347,7 +1361,7 @@ export function getStatuses(ids, success, error) {
}
export function getMyTeam(success, error) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/teams/me',
dataType: 'json',
type: 'GET',
@@ -1437,7 +1451,7 @@ export function listIncomingHooks(success, error) {
}
export function getAllPreferences(success, error) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/preferences/',
dataType: 'json',
type: 'GET',
@@ -1569,3 +1583,68 @@ export function removeLicenseFile(success, error) {
track('api', 'api_license_upload');
}
+
+export function getClientLicenceConfig(success, error) {
+ return $.ajax({
+ url: '/api/v1/license/client_config',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getClientLicenceConfig', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
+export function getInviteInfo(success, error, id) {
+ $.ajax({
+ url: '/api/v1/teams/get_invite_info',
+ type: 'POST',
+ dataType: 'json',
+ contentType: 'application/json',
+ data: JSON.stringify({invite_id: id}),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getInviteInfo', xhr, status, err);
+ if (error) {
+ error(e);
+ }
+ }
+ });
+}
+
+export function verifyEmail(success, error, uid, hid) {
+ $.ajax({
+ url: '/api/v1/users/verify_email',
+ type: 'POST',
+ contentType: 'application/json',
+ dataType: 'text',
+ data: JSON.stringify({uid, hid}),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('verifyEmail', xhr, status, err);
+ if (error) {
+ error(e);
+ }
+ }
+ });
+}
+
+export function resendVerification(success, error, teamName, email) {
+ $.ajax({
+ url: '/api/v1/users/resend_verification',
+ type: 'POST',
+ contentType: 'application/json',
+ dataType: 'text',
+ data: JSON.stringify({team_name: teamName, email}),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('resendVerification', xhr, status, err);
+ if (error) {
+ error(e);
+ }
+ }
+ });
+}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index daea9f43e..3de562b7b 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -42,13 +42,15 @@ export default {
RECEIVED_MSG: null,
- RECEIVED_TEAM: null,
+ RECEIVED_MY_TEAM: null,
RECEIVED_CONFIG: null,
RECEIVED_LOGS: null,
RECEIVED_SERVER_AUDITS: null,
RECEIVED_ALL_TEAMS: null,
+ RECEIVED_LOCALE: null,
+
SHOW_SEARCH: null,
TOGGLE_IMPORT_THEME_MODAL: null,
@@ -143,6 +145,7 @@ export default {
EMAIL_SERVICE: 'email',
SIGNIN_CHANGE: 'signin_change',
SIGNIN_VERIFIED: 'verified',
+ SESSION_EXPIRED: 'expired',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
POST_FOCUS_CONTEXT_RADIUS: 10,
@@ -267,7 +270,7 @@ export default {
buttonColor: '#FFFFFF',
mentionHighlightBg: '#984063',
mentionHighlightLink: '#A4FFEB',
- codeTheme: 'solarized_dark'
+ codeTheme: 'solarized-dark'
},
windows10: {
type: 'Windows Dark',
@@ -371,21 +374,6 @@ export default {
uiName: 'New Message Separator'
},
{
- group: 'linkAndButtonElements',
- id: 'linkColor',
- uiName: 'Link Color'
- },
- {
- group: 'linkAndButtonElements',
- id: 'buttonBg',
- uiName: 'Button BG'
- },
- {
- group: 'linkAndButtonElements',
- id: 'buttonColor',
- uiName: 'Button Text'
- },
- {
group: 'centerChannelElements',
id: 'mentionHighlightBg',
uiName: 'Mention Highlight BG'
@@ -401,11 +389,11 @@ export default {
uiName: 'Code Theme',
themes: [
{
- id: 'solarized_dark',
+ id: 'solarized-dark',
uiName: 'Solarized Dark'
},
{
- id: 'solarized_light',
+ id: 'solarized-light',
uiName: 'Solarized Light'
},
{
@@ -417,6 +405,21 @@ export default {
uiName: 'Monokai'
}
]
+ },
+ {
+ group: 'linkAndButtonElements',
+ id: 'linkColor',
+ uiName: 'Link Color'
+ },
+ {
+ group: 'linkAndButtonElements',
+ id: 'buttonBg',
+ uiName: 'Button BG'
+ },
+ {
+ group: 'linkAndButtonElements',
+ id: 'buttonColor',
+ uiName: 'Button Text'
}
],
DEFAULT_CODE_THEME: 'github',
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index 493916058..2b1aed9c0 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -193,6 +193,16 @@ class MattermostMarkdownRenderer extends marked.Renderer {
outHref = outHref.substring(1, outHref.length - 1);
}
+ try {
+ const unescaped = decodeURIComponent(unescape(href)).replace(/[^\w:]/g, '').toLowerCase();
+
+ if (unescaped.indexOf('javascript:') === 0 || unescaped.indexOf('vbscript:') === 0) { // eslint-disable-line no-script-url
+ return '';
+ }
+ } catch (e) {
+ return '';
+ }
+
if (!(/[a-z+.-]+:/i).test(outHref)) {
outHref = `http://${outHref}`;
}
@@ -548,3 +558,18 @@ export function format(text, options) {
return new MattermostParser(markdownOptions).parse(tokens);
}
+// Marked helper functions that should probably just be exported
+
+function unescape(html) {
+ return html.replace(/&([#\w]+);/g, (_, m) => {
+ const n = m.toLowerCase();
+ if (n === 'colon') {
+ return ':';
+ } else if (n.charAt(0) === '#') {
+ return n.charAt(1) === 'x' ?
+ String.fromCharCode(parseInt(n.substring(2), 16)) :
+ String.fromCharCode(+n.substring(1));
+ }
+ return '';
+ });
+}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 6942a8e08..88777164b 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -2,9 +2,10 @@
// See License.txt for license information.
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import UserStore from '../stores/user_store.jsx';
+import LocalizationStore from '../stores/localization_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
import TeamStore from '../stores/team_store.jsx';
import Constants from '../utils/constants.jsx';
@@ -941,7 +942,7 @@ export function updateAddressBar(channelName) {
}
export function switchChannel(channel) {
- EventHelpers.emitChannelClickEvent(channel);
+ GlobalActions.emitChannelClickEvent(channel);
updateAddressBar(channel.name);
@@ -1130,8 +1131,8 @@ export function fileSizeToString(bytes) {
// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server.
export function getFileUrl(filename, isDownload) {
- const downloadParam = isDownload ? '&download=1' : '';
- return getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + getSessionIndex() + downloadParam;
+ const downloadParam = isDownload ? '?download=1' : '';
+ return getWindowLocationOrigin() + '/api/v1/files/get' + filename + downloadParam;
}
// Gets the name of a file (including extension) from a given url or file path.
@@ -1151,14 +1152,6 @@ export function getWebsocketPort(protocol) {
return '';
}
-export function getSessionIndex() {
- if (global.window.mm_session_token_index >= 0) {
- return 'session_token_index=' + global.window.mm_session_token_index;
- }
-
- return '';
-}
-
// Generates a RFC-4122 version 4 compliant globally unique identifier.
export function generateId() {
// implementation taken from http://stackoverflow.com/a/2117523
@@ -1405,3 +1398,19 @@ export function isPostEphemeral(post) {
export function getRootId(post) {
return post.root_id === '' ? post.id : post.root_id;
}
+
+export function localizeMessage(id, defaultMessage) {
+ const translations = LocalizationStore.getTranslations();
+ if (translations) {
+ const value = translations[id];
+ if (value) {
+ return value;
+ }
+ }
+
+ if (defaultMessage) {
+ return defaultMessage;
+ }
+
+ return id;
+}
diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss
index f782da36b..76081710f 100644
--- a/web/sass-files/sass/partials/_admin-console.scss
+++ b/web/sass-files/sass/partials/_admin-console.scss
@@ -145,12 +145,27 @@
.form-group {
margin-bottom: 25px;
}
+ .file__upload {
+ position: relative;
+ margin: 0 10px 10px 0;
+ display: inline-block;
+ input {
+ position: absolute;
+ @include opacity(0);
+ width: 100%;
+ height: 100%;
+ z-index: 5;
+ top: 0;
+ left: 0;
+ }
+ }
.help-text {
+ &.no-margin {
+ margin: 0;
+ }
ul, ol {
padding-left: 23px;
}
- }
- .help-text {
margin: 10px 0 0 15px;
color: #777;
.help-link {
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 39cebe856..209f8e27f 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -58,23 +58,27 @@ body.ios {
box-shadow: none;
white-space: normal;
}
- .textbox-help-link {
+ .help__text {
+ right: 0;
position: absolute;
z-index: 3;
bottom: -23px;
font-size: 13px;
cursor: pointer;
}
- .textbox-help-link {
- right: 0;
+ .textbox-preview-link {
+ margin-right: 8px;
}
min-height:36px;
}
.help_format_text {
+ display: none !important;
position: absolute;
bottom: -23px;
- right: 40px;
+ left: 0px;
+ overflow: hidden;
+ text-overflow: ellipsis;
font-size: 0.85em;
@include opacity(0);
@include single-transition(all 0.2s ease);
@@ -391,15 +395,16 @@ body.ios {
top: -5px;
position: relative;
}
- .msg-typing {
- min-height: 25px;
- display: block;
- @include opacity(0.7);
- white-space: nowrap;
- width: 80%;
- overflow: hidden;
- text-overflow: ellipsis;
- }
+ }
+ .msg-typing {
+ display: block;
+ @include opacity(0.7);
+ white-space: nowrap;
+ margin-bottom: 5px;
+ overflow: hidden;
+ font-size: 0.95em;
+ text-overflow: ellipsis;
+ height: 20px;
}
}
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index a9a572768..06ce17041 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -573,8 +573,7 @@
.glyphicon-refresh-animate {
right: 33px;
top: 15px;
- color: #fff;
- color: rgba(255,255,255,0.5);
+ color: #aaa;
}
.form-control {
border: none;
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index 5e7f04724..44681291c 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -24,12 +24,6 @@
padding: 1em 1em 0;
display: none;
}
- > div {
- height: 100%;
- position: absolute;
- padding-bottom: 70px;
- width: 100%;
- }
.badge {
background-color: $primary-color;
position: absolute;
diff --git a/web/static/help/Messaging_en.md b/web/static/help/Messaging_en.md
deleted file mode 100644
index 2063ad41c..000000000
--- a/web/static/help/Messaging_en.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Messaging
-
-### Writing Messages
-
-You can write messages using the input box with the text "Write a message..." at the bottom of Mattermost.
-
-Press **ENTER** to send a message. Use **Shift+ENTER** to create a new line without sending a message.
-
-### Formatting Messages
-
-Mattermost messages are formatted using a standard called "markdown". Here are examples:
-
-| Text Entered | How it appears |
-|:---------------|:---------------|
-|`**bold**`| **bold** |
-| `_italic_`|_italic_|
-|`[hyperlink](http://mattermost.org)`|[hyperlink](http://mattermost.org)|
-|`![embedded image](https://travis-ci.org/mattermost/platform.svg)`|![embedded image](https://travis-ci.org/mattermost/platform.svg)|
-|`:smile:` `:sheep:` `:alien:`|:smile: :sheep: :alien:|
-
-Emojis provided free from [Emoji One](http://emojione.com/). Check out a full list of Emojis [here](http://emoji.codes/).
-
-
-### Mentioning Teammates
-
-You can mention a teammate by using the `@` symbol plus their username to send them a special notification to draw their attention.
-
-For example, you might write:
-
-```
-@alice how did your interview go with the new candidate?
-```
-
-Which sends a special mention notification to **alice** to check your message.
-
-To mention a teammate, press `@` and you should see a list of team members who can be messaged. You can either type their username or use the **Up** and **Down** arrow keys and then **ENTER** to select them to be mentioned.
-
-You can configure how you'd like to be alerted about mentions of your username, your first name, your nickname, or other keywords from **Account Settings** > **Notifications** and you can set channel-specific preferences from **[Channel Name]** > **Notification Preferences**
-
-### Messages Dropdown Menu
-
-To get to the Messages Dropdown Menu, hover over a message and click on the [...] menu. This shows a dropdown list containing additional actions you can perform on a message:
-
-- **Reply:** Opens up the sidebar so you can reply to a message in a comment thread.
-- **Permalink:** Creates a link to the message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives.
-- **Delete:** Deletes the message so it is no longer visible. Team Administrators and System Administrators can also delete another user's message.
-- **Edit:** Lets you edit your own message.
diff --git a/web/static/help/Messaging_es.md b/web/static/help/Messaging_es.md
deleted file mode 100644
index d3947f36a..000000000
--- a/web/static/help/Messaging_es.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Mensajes
-
-## Escribiendo Mensajes
-
-Puedes escribir mensajes utilizando el cuadro de texto que dice "Escribe un mensaje..." al final de Mattermost.
-
-Presiona **RETORNO** para enviar un mensaje. Utiliza **Shift+RETORNO** para crear una nueva linea sin enviar el mensaje.
-
-## Darle formato a los Mensajes
-
-Los mensajes de Mattermost se les asigna formato utilizando un estándard que se llama "markdown". Aquí algunos ejemplos:
-
-| Texto escrito | Como aparece |
-|:--------------|:-------------|
-|`**negrita**`| **negrita** |
-| `_italica_`|_italica_|
-|`[hipervinculo](http://mattermost.org)`|[hipervinculo](http://mattermost.org)|
-|`![imagen embebida](https://travis-ci.org/mattermost/platform.svg)`|![imagen embebida](https://travis-ci.org/mattermost/platform.svg)|
-|`:smile:` `:sheep:` `:alien:`|:smile: :sheep: :alien:|
-
-Revisa la lista completa de Emojis [aquí](http://www.emoji-cheat-sheet.com/).
-
-## Mencionando a compañeros
-
-Puedes mencionar a un compañero al utilizar el simbolo `@` más el nombre de usuario para enviarles una notificación especial que llame su atención.
-
-Por ejemplo, podrías escribir:
-
-```
-@alicia como te fue con la entrevista del nuevo candidato?
-```
-
-Lo cual enviará una notificación especial de mención a **alicia** para que lea tu mensaje.
-
-Para mencionar un compañero, presiona `@` y podrás ver una lista de los miembros de equipo a quienes puedes mandarles un mensaje. Puedes escribir su nombre de usuario o utilizar las flechas de **Arriba** y **Abajo** y presionar **RETORNO** para seleccionarlos.
-
-Puedes configurar como te gustaría ser notificado cuando alguien te menciona por nombre de usuario, tu primer nombre, sobrenombre o cualquier otra palabra clave en **Configurar Cuenta** > **Notificaciones** y puedes asignar preferencias especificas para un canal en **[Nombre del Canal]** > **Preferencias de Notificación**
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index 4c6fa0eae..2a536925c 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -1,11 +1,12 @@
{
"about.close": "Close",
"about.date": "Build Date:",
- "about.enterpriseEdition": "Enterprise Edition",
+ "about.enterpriseEditione1": "Enterprise Edition E1",
"about.hash": "Build Hash:",
"about.licensed": "Licensed by:",
"about.number": "Build Number:",
- "about.teamEdtion": "Team Edition",
+ "about.teamEditiont0": "Team Edition T0",
+ "about.teamEditiont1": "Team Edition T1",
"about.title": "About Mattermost",
"about.version": "Version:",
"access_history.title": "Access History",
@@ -152,14 +153,14 @@
"admin.ldap.bannerDesc": "If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.",
"admin.ldap.bannerHeading": "Note:",
"admin.ldap.baseDesc": "The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the LDAP tree.",
- "admin.ldap.baseEx": "Ex \"dc=mydomain,dc=com\"",
+ "admin.ldap.baseEx": "Ex \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "BaseDN:",
"admin.ldap.bindPwdDesc": "Password of the user given in \"Bind Username\".",
"admin.ldap.bindPwdTitle": "Bind Password:",
"admin.ldap.bindUserDesc": "The username used to perform the LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the LDAP tree specified in the BaseDN field.",
"admin.ldap.bindUserTitle": "Bind Username:",
"admin.ldap.emailAttrDesc": "The attribute in the LDAP server that will be used to populate the email addresses of users in Mattermost.",
- "admin.ldap.emailAttrEx": "Ex \"mail\"",
+ "admin.ldap.emailAttrEx": "Ex \"mail\" or \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Email Attribute:",
"admin.ldap.enableDesc": "When true, Mattermost allows login using LDAP",
"admin.ldap.enableTitle": "Enable Login With LDAP:",
@@ -191,11 +192,13 @@
"admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Username Attribute:",
"admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.",
+ "admin.license.chooseFile": "Choose File",
"admin.license.edition": "Edition: ",
"admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Designed for enterprise-scale communication.",
"admin.license.enterpriseType": "<div><p>This compiled release of Mattermost platform is provided under a <a href=\"http://mattermost.com\" target=\"_blank\">commercial license</a> from Mattermost, Inc. based on your subscription level and is subject to the <a href=\"{terms}\" target=\"_blank\">Terms of Service.</a></p><p>Your subscription details are as follows:</p>Name: {name}<br />Company or organization name: {company}<br/>Number of users: {users}<br/>License issued: {issued}<br/>Start date of license: {start}<br/>Expiry date of license: {expires}<br/>LDAP: {ldap}<br/></div>",
"admin.license.key": "License Key: ",
"admin.license.keyRemove": "Remove Enterprise License and Downgrade Server",
+ "admin.license.noFile": "No file uploaded",
"admin.license.removing": "Removing License...",
"admin.license.teamEdition": "Mattermost Team Edition. Designed for teams from 5 to 50 users.",
"admin.license.teamType": "<span><p>This compiled release of Mattermost platform is offered under an MIT license.</p><p>See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.</p></span>",
@@ -658,8 +661,10 @@
"email_signup.find": "Find my teams",
"email_verify.almost": "{siteName}: You are almost done",
"email_verify.notVerifiedBody": "Please verify your email address. Check your inbox for an email.",
+ "email_verify.verifyFailed": "Failed to verify your email.",
"email_verify.resend": "Resend Email",
"email_verify.sent": " Verification email sent.",
+ "email_verify.failed": " Failed to send verification email.",
"email_verify.verified": "{siteName} Email Verified",
"email_verify.verifiedBody": "<p>Your email has been verified! Click <a href={url}>here</a> to log in.</p>",
"error_bar.preview_mode": "Preview Mode: Email notifications have not been configured",
@@ -755,6 +760,7 @@
"login.or": "or",
"login.signTo": "Sign in to:",
"login.verified": " Email Verified",
+ "login.session_expired": " Your session has expired. Please login again.",
"login_email.badTeam": "Bad team name",
"login_email.email": "Email",
"login_email.emailReq": "An email is required",
@@ -819,16 +825,16 @@
"navbar_dropdown.teamSettings": "Team Settings",
"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 {teamDisplayName} {siteName} account.",
+ "password_form.enter": "Enter a new password for your {siteName} account.",
"password_form.error": "Please enter at least {chars} characters.",
"password_form.pwd": "Password",
"password_form.title": "Password Reset",
"password_form.update": "Your password has been updated successfully.",
"password_send.checkInbox": "Please check your inbox.",
- "password_send.description": "To reset your password, enter the email address you used to sign up for {teamName}.",
+ "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> for your <b>{teamDisplayName}</b> team on {hostname}.</p>",
+ "password_send.link": "<p>A password reset link has been sent to <b>{email}</b></p>",
"password_send.reset": "Reset my password",
"password_send.title": "Password Reset",
"post_attachment.collapse": "▲ collapse text",
@@ -1300,5 +1306,11 @@
"view_image.loading": "Loading ",
"view_image_popover.download": "Download",
"view_image_popover.file": "File {count} of {total}",
- "view_image_popover.publicLink": "Get Public Link"
+ "view_image_popover.publicLink": "Get Public Link",
+ "web.footer.about": "About",
+ "web.footer.help": "Help",
+ "web.footer.privacy": "Privacy",
+ "web.footer.terms": "Terms",
+ "web.header.back": "Back",
+ "web.root.singup_info": "All team communication in one place, searchable and accessible anywhere"
}
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index b14470872..f42dc879a 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -1,11 +1,12 @@
{
"about.close": "Cerrar",
"about.date": "Fecha de compilación:",
- "about.enterpriseEdition": "Edición Enterprise",
+ "about.enterpriseEditione1": "Edición Enterprise E1",
"about.hash": "Hash de compilación:",
"about.licensed": "Licenciado por:",
"about.number": "Número de compilación:",
- "about.teamEdtion": "Edición Team",
+ "about.teamEditiont0": "Edición Team T0",
+ "about.teamEditiont1": "Edición Team T1",
"about.title": "Acerca de Mattermost",
"about.version": "Versión:",
"access_history.title": "Historial de Acceso",
@@ -152,14 +153,14 @@
"admin.ldap.bannerDesc": "Si el atributo de un usuario cambia en el servidor LDAP será actualizado la próxima vez que el usuario ingrese sus credenciales para iniciar sesión en Mattermost. Esto incluye si un usuario se inactiva o se remueve en el servidor LDAP. Sincronización con servidores LDAP está planificado para futuras versiones.",
"admin.ldap.bannerHeading": "Nota:",
"admin.ldap.baseDesc": "El DN Base es el Nombre Distinguido de la ubicación donde Mattermost debe comenzar a buscar a los usuarios en el árbol del LDAP.",
- "admin.ldap.baseEx": "Ex \"dc=midominio,dc=com\"",
+ "admin.ldap.baseEx": "Ej \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "DN Base:",
"admin.ldap.bindPwdDesc": "Contraseña del usuario asignado en \"Usuario de Enlace\".",
"admin.ldap.bindPwdTitle": "Contraseña de Enlace:",
"admin.ldap.bindUserDesc": "El usuario que realizará las busquedas LDAP. Normalmente este debería ser una cuenta específicamente creada para ser utilizada por Mattermost. Debería contat con acceso limitado para leer la porción del árbol LDAP especificada en el campo DN Base.",
"admin.ldap.bindUserTitle": "Usuario de Enlace:",
"admin.ldap.emailAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar la dirección de correo electrónico de los usuarios en Mattermost.",
- "admin.ldap.emailAttrEx": "Ej \"mail\"",
+ "admin.ldap.emailAttrEx": "Ej \"mail\" o \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Atributo de Correo Electrónico:",
"admin.ldap.enableDesc": "Cuando es verdadero, Mattermost permite realizar inicio de sesión utilizando LDAP",
"admin.ldap.enableTitle": "Habilitar inicio de sesión con LDAP:",
@@ -191,11 +192,13 @@
"admin.ldap.usernameAttrEx": "Ej \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Atributo Usuario:",
"admin.licence.keyMigration": "Si estás migrando servidores es posible que necesites remover tu licencia de este servidor para poder instalarlo en un servidor nuevo. Para empezar, <a href=\"http://mattermost.com\" target=\"_blank\">deshabilita todas las características de la Edición Enterprise de este servidor</a>. Esta operación habilitará la opción para remover la licencia y degradar este servidor de la Edición Enterprise a la Edición Team.",
+ "admin.license.chooseFile": "Escoger Archivo",
"admin.license.edition": "Edición: ",
"admin.license.enterpriseEdition": "Mattermost Edición Enterprise. Diseñada para comunicación de escala empresarial.",
"admin.license.enterpriseType": "<div><p>Esta versión compilada de la plataforma de Mattermost es provista bajo una <a href=\"http://mattermost.com\" target=\"_blank\">licencia comercial</a> de Mattermost, Inc. en función en su nivel de subscripción y bajo los <a href=\"{terms}\" target=\"_blank\">Términos del Servicio.</a></p><p>Los detalles de tu subscripción son los siguientes:</p>Nombre: {name}<br />Nombre compañía u organización: {company}<br/>Cantidad de usuarios: {users}<br/>Licencia emitida: {issued}<br/>Fecha de inicio: {start}<br/>Fecha de expiración: {expires}<br/>LDAP: {ldap}<br/></div>",
"admin.license.key": "Llave de la Licencia: ",
"admin.license.keyRemove": "Remover la Licencia Enterprise y Degradar el Servidor",
+ "admin.license.noFile": "No se subió ningún archivo",
"admin.license.removing": "Removiendo Licencia...",
"admin.license.teamEdition": "Mattermost Edición Team. Diseñado para equipos desde 5 hasta 50 usuarios.",
"admin.license.teamType": "<span><p>Esta versión compilada de la plataforma de Mattermost es proporcionada bajo la licencia MIT.</p><p>Lea MIT-COMPILED-LICENSE.txt en el directorio raíz de la instalación para más detalles. Lea NOTICES.txt para información sobre software libre utilizado en este sistema.</p></span>",
@@ -819,16 +822,16 @@
"navbar_dropdown.teamSettings": "Configurar Equipo",
"password_form.change": "Cambiar mi contraseña",
"password_form.click": " Pincha <a href={url}>aquí</a> para iniciar sesión.",
- "password_form.enter": "Ingresa una nueva contraseña para tu cuenta en {teamDisplayName} {siteName}.",
+ "password_form.enter": "Ingresa una nueva contraseña para tu cuenta en {siteName}.",
"password_form.error": "Por favor ingresa al menos {chars} caracteres.",
"password_form.pwd": "Contraseña",
"password_form.title": "Restablecer Contraseña",
"password_form.update": "Tu contraseña ha sido actualizada satisfactoriamente.",
"password_send.checkInbox": "Por favor revisa tu bandeja de entrada.",
- "password_send.description": "Para restablecer tu contraseña, ingresa la dirección de correo electrónico que utilizaste para registrarte en {teamName}.",
+ "password_send.description": "Para restablecer tu contraseña, ingresa la dirección de correo electrónico que utilizaste para registrarte.",
"password_send.email": "Correo electrónico",
"password_send.error": "Por favor ingresa una dirección correo electrónico válida.",
- "password_send.link": "<p>Se ha enviado un enlace para restablecer la contraseña a <b>{email}</b> para tu equipo <b>{teamDisplayName}</b> en {hostname}.</p>",
+ "password_send.link": "<p>Se ha enviado un enlace para restablecer la contraseña a <b>{email}</b></p>",
"password_send.reset": "Restablecer mi contraseña",
"password_send.title": "Restablecer Contraseña",
"post_attachment.collapse": "▲ colapsar texto",
@@ -1300,5 +1303,11 @@
"view_image.loading": "Cargando ",
"view_image_popover.download": "Descargar",
"view_image_popover.file": "Archivo {count} de {total}",
- "view_image_popover.publicLink": "Obtener Enlace Público"
+ "view_image_popover.publicLink": "Obtener Enlace Público",
+ "web.footer.about": "Acerca",
+ "web.footer.help": "Ayuda",
+ "web.footer.privacy": "Privacidad",
+ "web.footer.terms": "Términos",
+ "web.header.back": "Atrás",
+ "web.root.singup_info": "Todas las comunicaciones del equipo en un sólo lugar, con búsquedas y accesible desde cualquier parte"
}
diff --git a/web/static/i18n/pt.json b/web/static/i18n/pt.json
index f4b998ded..f6210a6ae 100644
--- a/web/static/i18n/pt.json
+++ b/web/static/i18n/pt.json
@@ -1,11 +1,12 @@
{
"about.close": "Fechar",
"about.date": "Data De Criação:",
- "about.enterpriseEdition": "Enterprise Edition",
+ "about.enterpriseEditione1": "Enterprise Edition E1",
"about.hash": "Hash de Compilação:",
"about.licensed": "Licenciado pela:",
"about.number": "O Número De Compilação:",
- "about.teamEdtion": "Team Edition",
+ "about.teamEditiont0": "Team Edition T0",
+ "about.teamEditiont1": "Team Edition T1",
"about.title": "Sobre o Mattermost",
"about.version": "Versão:",
"access_history.title": "Histórico de Acesso",
@@ -26,7 +27,7 @@
"admin.email.allowEmailSignInDescription": "Quando verdadeiro, Mattermost permite aos usuários fazer login usando o e-mail e senha.",
"admin.email.allowEmailSignInTitle": "Permitir Login Com E-mail: ",
"admin.email.allowSignupDescription": "Quando verdadeiro, Mattermost permite a criação de equipe e conta de inscrição através de e-mail e senha. Este valor deve ser falso somente quando você deseja limitar a entrada para o single-sign-on service como OAuth ou LDAP.",
- "admin.email.allowSignupTitle": "Permitir Login com E-Mail: ",
+ "admin.email.allowSignupTitle": "Permitir Inscrição com E-Mail: ",
"admin.email.allowUsernameSignInDescription": "Quando verdadeiro, Mattermost permite os usuários fazer login usando seu nome de usuário e senha. Esta configuração é normalmente utilizado apenas quando a verificação de e-mail está desativada.",
"admin.email.allowUsernameSignInTitle": "Permitir Login Com Usuário: ",
"admin.email.connectionSecurityNone": "Nenhum",
@@ -43,7 +44,7 @@
"admin.email.false": "falso",
"admin.email.inviteSaltDescription": "32-caracteres salt adicionados a assinatura de convites por e-mail. Aleatoriamente gerados na instalação. Click \"Re-Gerar\" para criar um novo salt.",
"admin.email.inviteSaltExample": "Ex \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
- "admin.email.inviteSaltTitle": "Convidar Salt:",
+ "admin.email.inviteSaltTitle": "Salt Convite:",
"admin.email.notificationDisplayDescription": "Mostra o nome da conta de e-mail usada quando a notificação de e-mail é enviado do Mattermost.",
"admin.email.notificationDisplayExample": "Ex: \"Mattermost Notificação\", \"Sistema\", \"Não-Responda\"",
"admin.email.notificationDisplayTitle": "Notificação Nome de Exibição:",
@@ -152,14 +153,14 @@
"admin.ldap.bannerDesc": "Se um atributo de usuário de mudar no servidor LDAP, ele será atualizado na próxima vez que o usuário inserir suas credenciais para iniciar sessão no Mattermost. Isso inclui se um usuário estiver inativo ou removido de um servidor LDAP. Sincronização com servidores LDAP está prevista para um lançamento futuro.",
"admin.ldap.bannerHeading": "Nota:",
"admin.ldap.baseDesc": "Base DN é o nome distinto do local onde Mattermost deve começar sua busca para os usuários na árvore LDAP.",
- "admin.ldap.baseEx": "Ex \"dc=mydomain,dc=com\"",
+ "admin.ldap.baseEx": "Ex \"ou=Unit Name,dc=corp,dc=example,dc=com\"",
"admin.ldap.baseTitle": "BaseDN:",
"admin.ldap.bindPwdDesc": "Senha do usuário fornecido em \"Bind Username\".",
"admin.ldap.bindPwdTitle": "Vincular Senha:",
"admin.ldap.bindUserDesc": "O nome de usuário usado para realizar a pesquisa LDAP. Isso deve ser tipicamente uma conta criada especificamente para uso do Mattermost. Deve ter acesso limitado a ler a parte da árvore LDAP especificado no campo BaseDN.",
"admin.ldap.bindUserTitle": "Bind Username:",
"admin.ldap.emailAttrDesc": "O atributo no servidor LDAP que será usado para preencher os endereços de e-mail de usuários no Mattermost.",
- "admin.ldap.emailAttrEx": "Ex \"mail\"",
+ "admin.ldap.emailAttrEx": "Ex \"mail\" ou \"userPrincipalName\"",
"admin.ldap.emailAttrTitle": "Atributo de E-mail:",
"admin.ldap.enableDesc": "Quando verdadeiro, Mattermost permite login utilizando LDAP",
"admin.ldap.enableTitle": "Ativar Login With LDAP:",
@@ -191,11 +192,13 @@
"admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Atributo do Usuário:",
"admin.licence.keyMigration": "Se você estiver migrando seu servidor você pode precisar remover sua chave da licença deste servidor a pedido para instala-la em um novo servidor. Para iniciar, <a href=\"http://mattermost.com\" target=\"_blank\">desativar todos os recursos Enterprise Edition deste servidor</a>. Isto irá habilitar para remover a chave da licença e fazer downgrade deste servidor Enterprise Edition para Team Edition.",
+ "admin.license.chooseFile": "Escolha um Arquivo",
"admin.license.edition": "Edição: ",
"admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Desenvolvido para escala empresarial de comunicação.",
"admin.license.enterpriseType": "<div><p>Esta versão compilada da plataforma Mattermost é fornecida sob a <a href=\"http://mattermost.com\" target=\"_blank\">licença comercial</a> para Mattermost, Inc. com base em seu nível de subscrição e está sujeito a <a href=\"{terms}\" target=\"_blank\">Termos de Serviço.</a></p><p>Os detalhes de sua assinatura, são como segue:</p>Nome: {name}<br />Nome da Empresa ou organização: {company}<br/>Número de usuários: {users}<br/>Licença emitida: {issued}<br/>Data de Início da licença: {start}<br/>Data de expiração da licença: {expires}<br/>LDAP: {ldap}<br/></div>",
"admin.license.key": "Chave da Licença: ",
"admin.license.keyRemove": "Remover a Licença Enterprise e fazer Downgrade do Servidor",
+ "admin.license.noFile": "Nenhum arquivo enviado",
"admin.license.removing": "Removendo a Licença...",
"admin.license.teamEdition": "Mattermost Team Edition. Desenvolvido para equipes de 5 a 50 usuários.",
"admin.license.teamType": "<span><p>Esta versão compilada da plataforma Mattermost é oferecido sob uma licença MIT.</p><p>Ver MIT-COMPILED-LICENSE.txt no raiz do diretório de instalação para obter detalhes. Ver NOTICES.txt para obter informações sobre o software open source usados neste sistema.</p></span>",
@@ -205,7 +208,7 @@
"admin.license.uploadDesc": "Enviar uma chave da licença para Mattermost Enterprise Edition para fazer upgrade deste servidor. <a href=\"http://mattermost.com\" target=\"_blank\">Visite-nos online</a> para saber mais sobre os beneficios da Enterprise Edition ou para comprar uma chave.",
"admin.license.uploading": "Enviando Licença...",
"admin.log.consoleDescription": "Normalmente definido como falso em produção. Os desenvolvedores podem definir este campo como verdadeiro para mensagens de log de saída no console baseado na opção de nível de console. Se verdadeiro, o servidor escreve mensagens para o fluxo de saída padrão (stdout).",
- "admin.log.consoleTitle": "Log Do Console: ",
+ "admin.log.consoleTitle": "Log Para o Console: ",
"admin.log.false": "falso",
"admin.log.fileDescription": "Normalmente definido como verdadeiro em produção. Quando verdadeiro, arquivos de log são gravados no arquivo de log especificado no campo de localização abaixo.",
"admin.log.fileLevelDescription": "Esta configuração determina o nível de detalhe que são gravados no log de eventos no console. ERROR: Saídas somente mensagens de erro. INFO: Saídas de mensagens de erro e informações em torno de inicialização. DEBUG: Impressões de alto detalhe para desenvolvedores que trabalham na depuração de problemas.",
@@ -277,6 +280,9 @@
"admin.service.attemptTitle": "Máxima Tentativas de Login:",
"admin.service.cmdsDesc": "Quando verdadeiro, comandos slash criados por usuários serão permitidos.",
"admin.service.cmdsTitle": "Ativar Comandos Slash: ",
+ "admin.service.corsDescription": "Ativar requisição de origem HTTP Cross dos domínios especificados (separados por espaço). Usar \"*\" se você quiser permitir CORS de qualquer domínio ou deixe em branco para desativar.",
+ "admin.service.corsEx": "http://example.com https://example.com",
+ "admin.service.corsTitle": "Permitir Requisição Cross-origin de:",
"admin.service.developerDesc": "(Opção dos desenvolvedores) Quando verdadeira, a informação extra em torno dos erros será exibida na UI.",
"admin.service.developerTitle": "Ativar o Modo Desenvolvedor: ",
"admin.service.false": "falso",
@@ -558,6 +564,7 @@
"channel_loader.wrote": " escreveu: ",
"channel_members_modal.addNew": " Adicionar Novos Membros",
"channel_members_modal.close": "Fechar",
+ "channel_members_modal.remove": "Remover",
"channel_memebers_modal.members": " Membros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
@@ -589,6 +596,7 @@
"choose_auth_page.find": "Encontrar minhas equipes",
"choose_auth_page.gitlabCreate": "Criar uma equipe com uma conta GitLab",
"choose_auth_page.googleCreate": "Criar nova equipe com a Conta do Google Apps",
+ "choose_auth_page.ldapCreate": "Criar uma nova equipe com uma conta LDAP",
"choose_auth_page.noSignup": "Nenhum método de inscrição configurado, por favor contate seu administrador do sistema.",
"claim.account.noEmail": "Nenhum email específicado",
"claim.email_to_sso.enterPwd": "Entre a senha para o sua conta {team} {site}",
@@ -665,8 +673,8 @@
"file_upload.filesAbove": "Arquivos acima {max}MB não podem ser enviados: {filenames}",
"file_upload.limited": "Limite máximo de uploads de {count} arquivos. Por favor use um post adicional para mais arquivos.",
"file_upload.pasted": "Imagem Colada em ",
- "filtered_user_list.count": "{count, number} {count, plural, one {Membro} other {Membros}}",
- "filtered_user_list.countTotal": "{count, number} {count, plural, one {Membro} other {Membros}} de {total} Total",
+ "filtered_user_list.count": "{count, number} {count, plural, one {membro} other {membros}}",
+ "filtered_user_list.countTotal": "{count, number} {count, plural, one {membro} other {membros}} de {total} Total",
"filtered_user_list.search": "Procurar membros",
"find_team.email": "E-mail",
"find_team.findDescription": "Foi enviado um e-mail com links para todas as equipes do qual você é membro.",
@@ -690,7 +698,7 @@
"general_tab.regenerate": "Re-Gerar",
"general_tab.required": "Este campo é obrigatório",
"general_tab.teamName": "Nome da Equipe",
- "general_tab.teamNameInfo": "Defina o nome da equipe como aparece na sua tela inicial e no topo na lateral esquerda.",
+ "general_tab.teamNameInfo": "Defina o nome da equipe como aparece na sua tela de login e no topo na lateral esquerda.",
"general_tab.title": "Definições Gerais",
"general_tab.yes": "Sim",
"get_link.clipboard": " Link copiado para a área de transferência.",
@@ -698,6 +706,7 @@
"get_link.copy": "Copiar Link",
"get_post_link_modal.help": "O link abaixo permite que usuários autorizados possam ver seus posts.",
"get_post_link_modal.title": "Copiar Permalink",
+ "get_team_invite_link_modal.help": "Enviar o link abaixo para sua equipe de trabalho para que eles se inscrevam no site da sua equipe. O Link de Convite de Equipe como ele não muda pode ser compartilhado com várias pessoas ao menos que seja re-gerado em Configurações de Equipe pelo Administrador de Equipe.",
"get_team_invite_link_modal.helpDisabled": "Criação de usuários está desabilitada para sua equipe. Por favor peça ao administrador de equipe por detalhes.",
"get_team_invite_link_modal.title": "Link para Convite de Equipe",
"intro_messages.DM": "Este é o início do seu histórico de mensagens diretas com {teammate}.<br />Mensagens diretas e arquivos compartilhados aqui não são mostrados para pessoas de fora desta área.",
@@ -731,6 +740,11 @@
"invite_member.send2": "Enviar Convites",
"invite_member.sending": " Enviando",
"invite_member.teamInviteLink": "Você também pode convidar pessoas usando o {link}",
+ "ldap_signup.find": "Encontrar minhas equipes",
+ "ldap_signup.ldap": "Criar uma equipe com uma conta LDAP",
+ "ldap_signup.length_error": "O nome deve ser de 3 ou mais caracteres até um máximo de 15",
+ "ldap_signup.teamName": "Entre o nome da nova equipe",
+ "ldap_signup.team_error": "Por favor entre o nome da equipe",
"loading_screen.loading": "Carregando",
"login.changed": " Método de login alterada com sucesso",
"login.create": "Crie um agora",
@@ -742,7 +756,7 @@
"login.noAccount": "Não tem uma conta? ",
"login.on": "no {siteName}",
"login.or": "ou",
- "login.signTo": "Entrar em:",
+ "login.signTo": "Login em:",
"login.verified": " Email Verificado",
"login_email.badTeam": "Nome ruim de equipe",
"login_email.email": "E-mail",
@@ -766,6 +780,7 @@
"login_username.verifyEmailError": "Por favor verifique seu endereço de email. Verifique por um email na sua caixa de entrada.",
"member_item.makeAdmin": "Tornar Admin",
"member_item.member": "Membro",
+ "member_list.noUsersAdd": "Nenhum usuário para adicionar.",
"members_popover.msg": "Mensagem",
"members_popover.title": "Membros",
"more_channels.close": "Fechar",
@@ -807,16 +822,15 @@
"navbar_dropdown.teamSettings": "Configurações da Equipe",
"password_form.change": "Alterar minha senha",
"password_form.click": "Clique <a href={url}>aqui</a> para logar.",
- "password_form.enter": "Entre uma nova senha para sua conta {teamDisplayName} {siteName}.",
+ "password_form.enter": "Entre uma nova senha para sua conta {siteName}.",
"password_form.error": "Por favor, insira pelo menos {chars} caracteres.",
"password_form.pwd": "Senha",
"password_form.title": "Resetar Senha",
"password_form.update": "Sua senha foi atualizada com sucesso.",
"password_send.checkInbox": "Por favor verifique sua caixa de entrada.",
- "password_send.description": "Para resetar sua senha, entre o endereço de email que você usou para se inscrever em {teamName}.",
+ "password_send.description": "Para resetar sua senha, entre o endereço de email que você usou para se inscrever.",
"password_send.email": "E-mail",
"password_send.error": "Por favor entre um endereço de e-mail válido.",
- "password_send.link": "<p>Um link para resetar a sua senha na equipe <b>{teamDisplayName}</b> em {hostname} foi enviado para <b>{email}</b>.</p>",
"password_send.reset": "Resetar minha senha",
"password_send.title": "Resetar Senha",
"post_attachment.collapse": "▲ recolher texto",
@@ -936,7 +950,7 @@
"signup_user_completed.chooseUser": "Escolha o seu nome de usuário",
"signup_user_completed.create": "Criar Conta",
"signup_user_completed.emailHelp": "Email valido necessário para inscrição",
- "signup_user_completed.emailIs": "Seu endereço de e-mail é <strong>{email}</strong>. Você irá usar esse endereço para entrar em {siteName}.",
+ "signup_user_completed.emailIs": "Seu endereço de e-mail é <strong>{email}</strong>. Você irá usar esse endereço para logar no {siteName}.",
"signup_user_completed.expired": "Você já concluiu o processo de inscrição para este convite ou este convite expirou.",
"signup_user_completed.gitlab": "com GitLab",
"signup_user_completed.google": "com Google",
@@ -960,6 +974,7 @@
"sso_signup.team_error": "Por favor entre o nome da equipe",
"suggestion.mention.all": "Notificar todo mundo na equipe",
"suggestion.mention.channel": "Notifica todos no canal",
+ "suggestion.search.private": "Grupos Privados",
"suggestion.search.public": "Canais Públicos",
"team_export_tab.download": "download",
"team_export_tab.export": "Exportar",
@@ -999,7 +1014,7 @@
"team_signup_display_name.required": "Este campo é obrigatório",
"team_signup_display_name.teamName": "Nome Da Equipe",
"team_signup_email.address": "Endereço de E-mail",
- "team_signup_email.different": "Por favor, use um e-mail diferente do que o usado no login",
+ "team_signup_email.different": "Por favor, use um e-mail diferente do que o usado na inscrição",
"team_signup_email.validEmail": "Por favor entre um endereço de e-mail válido",
"team_signup_password.agreement": "Ao prosseguir para criar sua conta e usar {siteName}, você concorda com nosso <a href='/static/help/terms.html'>Termo de Serviço</a> e <a href='/static/help/privacy.html'>Politica de Privacidade</a>. Se você não concorda, você não pode usar {siteName}.",
"team_signup_password.back": "Voltar para o passo anterior",
@@ -1052,9 +1067,9 @@
"textbox.help": "Ajuda",
"textbox.inlinecode": "`código`",
"textbox.italic": "_itálico_",
- "textbox.preformatted": "```pre-formatado```",
+ "textbox.preformatted": "```pré-formatado```",
"textbox.preview": "Pré-visualização",
- "textbox.quote": ">citado",
+ "textbox.quote": ">citar",
"textbox.strike": "tachado",
"tutorial_intro.allSet": "Está tudo pronto",
"tutorial_intro.end": "Clique em “Próximo” para entrar Town Square. Este é o primeiro canal que sua equipe de trabalho vê quando eles se inscrevem. Use para postar atualizações que todos precisam saber.",
@@ -1272,7 +1287,7 @@
"user.settings.security.logoutActiveSessions": "Ver e fazer Logout das Sessões Ativas",
"user.settings.security.method": "Método de Login",
"user.settings.security.newPassword": "Nova Senha",
- "user.settings.security.oneSignin": "Você pode ter somente um método de inscrição por vez. Trocando o método de inscrição será enviado um email de notificação se você alterar com sucesso.",
+ "user.settings.security.oneSignin": "Você pode ter somente um método de login por vez. Trocando o método de login será enviado um email de notificação se você alterar com sucesso.",
"user.settings.security.password": "Senha",
"user.settings.security.passwordLengthError": "Novas senhas precisam ter pelo menos {chars} characters",
"user.settings.security.passwordMatchError": "As novas senhas que você inseriu não correspondem",
diff --git a/web/static/images/themes/code_themes/solarized_dark.png b/web/static/images/themes/code_themes/solarized-dark.png
index 582df48f9..582df48f9 100644
--- a/web/static/images/themes/code_themes/solarized_dark.png
+++ b/web/static/images/themes/code_themes/solarized-dark.png
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_light.png b/web/static/images/themes/code_themes/solarized-light.png
index d2c2702fb..d2c2702fb 100644
--- a/web/static/images/themes/code_themes/solarized_light.png
+++ b/web/static/images/themes/code_themes/solarized-light.png
Binary files differ
diff --git a/web/templates/admin_console.html b/web/templates/admin_console.html
deleted file mode 100644
index 08c90493e..000000000
--- a/web/templates/admin_console.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-{{define "admin_console"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body>
-<script src="/static/js/Chart.min.js"></script>
-
-<div id='admin_controller'></div>
-
-<script>
- window.setup_admin_console_page({{ .Props }});
-
- $(document).ready(function(){
- $('[data-toggle="tooltip"]').tooltip();
- $('[data-toggle="popover"]').popover();
- });
-</script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/authorize.html b/web/templates/authorize.html
deleted file mode 100644
index 0fa36b0ab..000000000
--- a/web/templates/authorize.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{{define "authorize"}}
-<html>
-{{template "head" . }}
-<body>
- <div id="authorize">
- </div>
- <script>
- window.setup_authorize_page({{.Props}});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/channel.html b/web/templates/channel.html
deleted file mode 100644
index 94d79a022..000000000
--- a/web/templates/channel.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-{{define "channel"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body>
- <div id="channel_view" class='channel-view'></div>
-<script>
- window.setup_channel_page({{ .Props }}, {{ .Team }}, {{ .Channel }});
- $('body').tooltip( {selector: '[data-toggle=tooltip]'} );
- var modals = $('.modal-body').not('.edit-modal-body');
- if($(window).height() > 1200){
- modals.css('max-height', 1000);
- } else {
- modals.css('max-height', $(window).height() - 200);
- }
- modals.perfectScrollbar();
-</script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/claim_account.html b/web/templates/claim_account.html
deleted file mode 100644
index 2a9126d1b..000000000
--- a/web/templates/claim_account.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{{define "claim_account"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/{{.Props.TeamName}}"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="signup-team__container">
- <img class="signup-team-logo" src="/static/images/logo.png" />
- <div id="claim"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- <div>
- </div>
- <script>
- window.setup_claim_account_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/docs.html b/web/templates/docs.html
deleted file mode 100644
index dc18e5cb6..000000000
--- a/web/templates/docs.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{{define "docs"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
-<div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="docs__page" id="docs"></div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
-</div>
-<script>
- window.setup_documentation_page({{ .Props }});
-</script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/find_team.html b/web/templates/find_team.html
deleted file mode 100644
index b7e1d7eca..000000000
--- a/web/templates/find_team.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{{define "find_team"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="signup-team__container">
- <img class="signup-team-logo" src="/static/images/logo.png" />
- <div id="find-team"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
- window.setup_find_team_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/footer.html b/web/templates/footer.html
deleted file mode 100644
index 5b11328fb..000000000
--- a/web/templates/footer.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{{define "footer"}}
-<div class="footer-pane col-xs-12">
- <div class="col-xs-12">
- <span class="pull-right footer-site-name">{{ .ClientCfg.SiteName }}</span>
- </div>
- <div class="col-xs-12">
- <span class="pull-right footer-link copyright">© 2015 Mattermost, Inc.</span>
- <a id="help_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterHelp }}</a>
- <a id="terms_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterTerms }}</a>
- <a id="privacy_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterPrivacy }}</a>
- <a id="about_link" class="pull-right footer-link" href="#">{{ .ClientCfg.FooterAbout }}</a>
- </div>
-</div>
-<script>
- if (window.mm_config.HelpLink) {
- document.getElementById("help_link").setAttribute("href", window.mm_config.HelpLink);
- } else {
- $("#help_link").remove();
- }
-
- if (window.mm_config.TermsOfServiceLink) {
- document.getElementById("terms_link").setAttribute("href", window.mm_config.TermsOfServiceLink);
- } else {
- $("#terms_link").remove();
- }
-
- if (window.mm_config.PrivacyPolicyLink) {
- document.getElementById("privacy_link").setAttribute("href", window.mm_config.PrivacyPolicyLink);
- } else {
- $("#privacy_link").remove();
- }
-
- if (window.mm_config.AboutLink) {
- document.getElementById("about_link").setAttribute("href", window.mm_config.AboutLink);
- } else {
- $("#about_link").remove();
- }
-</script>
-{{end}}
diff --git a/web/templates/head.html b/web/templates/head.html
deleted file mode 100644
index 61b1aa12b..000000000
--- a/web/templates/head.html
+++ /dev/null
@@ -1,191 +0,0 @@
-{{define "head"}}
-<head>
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
- <meta name="robots" content="noindex, nofollow">
- <meta name="referrer" content="no-referrer">
-
- <title>{{ .Props.Title }}</title>
-
- <!-- iOS add to homescreen -->
- <meta name="apple-mobile-web-app-capable" content="yes" />
- <meta name="apple-mobile-web-app-status-bar-style" content="default">
- <meta name="mobile-web-app-capable" content="yes" />
- <meta name="apple-mobile-web-app-title" content="{{ .Props.Title }}">
- <meta name="application-name" content="{{ .Props.Title }}">
- <meta name="format-detection" content="telephone=no">
- <!-- iOS add to homescreen -->
-
- <!-- Android add to homescreen -->
- <link rel="apple-touch-icon" sizes="57x57" href="/static/images/favicon/apple-touch-icon-57x57.png">
- <link rel="apple-touch-icon" sizes="60x60" href="/static/images/favicon/apple-touch-icon-60x60.png">
- <link rel="apple-touch-icon" sizes="72x72" href="/static/images/favicon/apple-touch-icon-72x72.png">
- <link rel="apple-touch-icon" sizes="76x76" href="/static/images/favicon/apple-touch-icon-76x76.png">
- <link rel="apple-touch-icon" sizes="114x114" href="/static/images/favicon/apple-touch-icon-114x114.png">
- <link rel="apple-touch-icon" sizes="120x120" href="/static/images/favicon/apple-touch-icon-120x120.png">
- <link rel="apple-touch-icon" sizes="144x144" href="/static/images/favicon/apple-touch-icon-144x144.png">
- <link rel="apple-touch-icon" sizes="152x152" href="/static/images/favicon/apple-touch-icon-152x152.png">
- <link rel="apple-touch-icon" sizes="180x180" href="/static/images/favicon/apple-touch-icon-180x180.png">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="192x192" href="/static/images/favicon/android-chrome-192x192.png">
- <link rel="icon" type="image/png" sizes="96x96" href="/static/images/favicon/favicon-96x96.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon/favicon-16x16.png">
- <link rel="manifest" href="/static/config/manifest.json">
- <!-- Android add to homescreen -->
-
- <!-- CSS Should always go first -->
- <link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
- <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css">
- <link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css">
- <link rel="stylesheet" href="/static/css/styles.css">
- <link rel="stylesheet" href="/static/css/google-fonts.css">
- <link rel="stylesheet" href="/static/css/katex.min.css">
- <link rel="stylesheet" class="code_theme" href="">
-
- <script src="/static/js/intl-1.0.0/Intl.js"></script>
- <script src="/static/js/intl-1.0.0/locale-data/jsonp/en.js"></script>
- <script src="/static/js/intl-1.0.0/locale-data/jsonp/es.js"></script>
- <script src="/static/js/intl-1.0.0/locale-data/jsonp/pt.js"></script>
-
- <script src="/static/js/react-0.14.3.js"></script>
- <script src="/static/js/react-dom-0.14.3.js"></script>
- <script src="/static/js/react-intl-2.0.0-beta-2/react-intl.js"></script>
- <script src="/static/js/react-intl-2.0.0-beta-2/locale-data/en.js"></script>
- <script src="/static/js/react-intl-2.0.0-beta-2/locale-data/es.js"></script>
- <script src="/static/js/react-intl-2.0.0-beta-2/locale-data/pt.js"></script>
- <script src="/static/js/jquery-2.1.4.js"></script>
- <script src="/static/js/bootstrap-3.3.5.js"></script>
- <script src="/static/js/bootstrap-colorpicker.min.js"></script>
- <script src="/static/js/react-bootstrap-0.28.1.js"></script>
- <script src="/static/js/velocity.min.js"></script>
- <script src="/static/js/perfect-scrollbar-0.6.7.jquery.min.js"></script>
- <script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
- <script src="/static/js/babel-polyfill-6.1.18.min.js"></script>
- <script src="/static/js/katex.min.js"></script>
-
- <style id="antiClickjack">body{display:none !important;}</style>
-
- <script>
- if ('ReactIntl' in window && 'ReactIntlLocaleData' in window) {
- Object.keys(ReactIntlLocaleData).forEach(function(lang) {
- ReactIntl.addLocaleData(ReactIntlLocaleData[lang]);
- });
- }
-
- window.mm_config = {{ .ClientCfg }};
- window.mm_license = {{ .ClientLicense }};
- window.mm_team = {{ .Team }};
- window.mm_user = {{ .User }};
- window.mm_channel = {{ .Channel }};
- window.mm_locale = {{ .Locale }};
- window.mm_preferences = {{ .Preferences }};
-
- $(function() {
- if (window.mm_preferences != null) {
- PreferenceStore.setPreferences(window.mm_preferences);
- PreferenceStore.emitChange();
- }
- });
-
- if ({{.SessionTokenIndex}} >= 0) {
- window.mm_session_token_index = {{.SessionTokenIndex}};
- $.ajaxSetup({
- headers: {
- 'X-MM-TokenIndex': mm_session_token_index,
- 'Accept-Language': mm_locale
- }
- });
- } else {
- $.ajaxSetup({
- headers: {
- 'Accept-Language': mm_locale
- }
- });
- }
-
- $(function () {
- $(window).bind('storage', function (e) {
- // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
- if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
- // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (window.BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
- return;
- }
-
- console.log('detected logout from a different tab');
- window.location.href = '/' + window.mm_team.name;
- }
-
- if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
- // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (window.BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
- return;
- }
-
- console.log('detected login from a different tab');
- location.reload();
- }
- });
- });
-
- $(window).on('beforeunload', function(){
- if (window.SocketStore) {
- SocketStore.close();
- }
- });
- </script>
-
- <script>
- window.onerror = function(msg, url, line, column, stack) {
- var l = {};
- l.level = 'ERROR';
- l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
-
- $.ajax({
- url: '/api/v1/admin/log_client',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(l)
- });
-
- if (window.mm_config.EnableDeveloper === 'true') {
- window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'});
- window.ErrorStore.emitChange();
- }
- }
- </script>
-
- <script src="/static/js/libs.min.js"></script>
- <script src="/static/js/bundle.js"></script>
-
- <script type="text/javascript">
- if (self === top) {
- var blocker = document.getElementById("antiClickjack");
- blocker.parentNode.removeChild(blocker);
- }
- </script>
-
- <script type="text/javascript">
- if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
- !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
- analytics.load(window.mm_config.SegmentDeveloperKey);
- if (window.mm_user) {
- analytics.identify(window.mm_user.id, {
- name: window.mm_user.nickname,
- email: window.mm_user.email,
- createdAt: window.mm_user.create_at,
- username: window.mm_user.username,
- team_id: window.mm_user.team_id,
- id: window.mm_user.id
- });
- }
- analytics.page();
- }}();
- } else {
- analytics = {};
- analytics.page = function(){};
- analytics.track = function(){};
- }
- </script>
-</head>
-{{end}}
diff --git a/web/templates/home.html b/web/templates/home.html
deleted file mode 100644
index 08876d41d..000000000
--- a/web/templates/home.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{{define "home"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body>
- <div class="container-fluid">
- <div class="sidebar--right" id="sidebar-right"></div>
- <div class="sidebar--left" id="sidebar-left"></div>
- <div class="inner__wrap">
- <div class="row header">
- <div id="navbar"></div>
- </div>
- <div class="row main">
- <div class="hidden-xs" id="sidebar"></div>
- <div class="app__content"></div>
- </div>
- </div>
- </div>
- <script>
- window.setup_home_page();
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/login.html b/web/templates/login.html
deleted file mode 100644
index 88540a906..000000000
--- a/web/templates/login.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{{define "login"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div id="login"></div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
- window.setup_login_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/password_reset.html b/web/templates/password_reset.html
deleted file mode 100644
index e68f8b693..000000000
--- a/web/templates/password_reset.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{{define "password_reset"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/{{.Props.TeamName}}"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="signup-team__container">
- <img class="signup-team-logo" src="/static/images/logo.png" />
- <div id="reset"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
- window.setup_password_reset_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html
deleted file mode 100644
index afba58066..000000000
--- a/web/templates/signup_team.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{{define "signup_team"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="col-sm-12">
- <div class="signup-team__container">
- <img class="signup-team-logo" src="/static/images/logo.png" />
- <h1>{{ .ClientCfg.SiteName }}</h1>
- <h4 class="color--light">{{.Props.Info}}</h4>
- <div id="signup-team"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
-window.setup_signup_team_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/signup_team_complete.html b/web/templates/signup_team_complete.html
deleted file mode 100644
index 3873d8978..000000000
--- a/web/templates/signup_team_complete.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{{define "signup_team_complete"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/{{.Props.TeamName}}"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="signup-team__container">
- <div id="signup-team-complete"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
-window.setup_signup_team_complete_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/signup_team_confirm.html b/web/templates/signup_team_confirm.html
deleted file mode 100644
index 31f1ba95b..000000000
--- a/web/templates/signup_team_confirm.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{{define "signup_team_confirm"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/{{.Props.TeamName}}"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div id="signup-team-confirm"></div>
- </div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
-window.setup_signup_team_confirm_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/signup_user_complete.html b/web/templates/signup_user_complete.html
deleted file mode 100644
index 937a89dd2..000000000
--- a/web/templates/signup_user_complete.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{{define "signup_user_complete"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/{{.Props.TeamName}}"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="signup-team__container padding--less">
- <div id="signup-user-complete"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
- window.setup_signup_user_complete_page({{ .Props }});
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/templates/verify.html b/web/templates/verify.html
deleted file mode 100644
index 2e5496d7a..000000000
--- a/web/templates/verify.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{{define "verify"}}
-<!DOCTYPE html>
-<html>
- {{template "head" . }}
- <body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="signup-header">
- <a href="/{{.Props.TeamName}}"><span class='fa fa-chevron-left'></span>{{ .ClientCfg.HeaderBack }}</a>
- </div>
- <div class="col-sm-12">
- <div class="signup-team__container">
- <img class="signup-team-logo" src="/static/images/logo.png" />
- <div id="verify"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
- window.setupVerifyPage({{ .Props }});
- </script>
- </body>
-</html>
-{{end}}
diff --git a/web/web.go b/web/web.go
index 09450b976..2a44ece00 100644
--- a/web/web.go
+++ b/web/web.go
@@ -4,67 +4,16 @@
package web
import (
- "fmt"
+ "net/http"
+ "strings"
+
l4g "github.com/alecthomas/log4go"
- "github.com/gorilla/mux"
"github.com/mattermost/platform/api"
"github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"github.com/mssola/user_agent"
- "gopkg.in/fsnotify.v1"
- "html/template"
- "net/http"
- "net/url"
- "strconv"
- "strings"
)
-var Templates *template.Template
-
-type HtmlTemplatePage api.Page
-
-func NewHtmlTemplatePage(templateName string, title string, locale string) *HtmlTemplatePage {
-
- if len(title) > 0 {
- title = utils.Cfg.TeamSettings.SiteName + " - " + title
- }
-
- props := make(map[string]string)
- props["Title"] = title
- return &HtmlTemplatePage{
- TemplateName: templateName,
- Props: props,
- ClientCfg: utils.ClientCfg,
- ClientLicense: utils.ClientLicense,
- Locale: locale,
- }
-}
-
-func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) {
- if me.Team != nil {
- me.Team.Sanitize()
- }
-
- if me.User != nil {
- me.User.Sanitize(map[string]bool{})
- me.Locale = me.User.Locale
- }
-
- me.Props["Locale"] = me.Locale
- me.SessionTokenIndex = c.SessionTokenIndex
-
- me.ClientCfg["HeaderBack"] = c.T("web.header.back")
- me.ClientCfg["FooterHelp"] = c.T("web.footer.help")
- me.ClientCfg["FooterTerms"] = c.T("web.footer.terms")
- me.ClientCfg["FooterPrivacy"] = c.T("web.footer.privacy")
- me.ClientCfg["FooterAbout"] = c.T("web.footer.about")
-
- if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil {
- c.SetUnknownError(me.TemplateName, err.Error())
- }
-}
-
func InitWeb() {
l4g.Debug(utils.T("web.init.debug"))
@@ -74,81 +23,7 @@ func InitWeb() {
l4g.Debug("Using static directory at %v", staticDir)
mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
- mainrouter.Handle("/", api.AppHandlerIndependent(root)).Methods("GET")
- mainrouter.Handle("/oauth/authorize", api.UserRequired(authorizeOAuth)).Methods("GET")
- mainrouter.Handle("/oauth/access_token", api.ApiAppHandler(getAccessToken)).Methods("POST")
-
- mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET")
- mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET")
- mainrouter.Handle("/signup_team_confirm/", api.AppHandlerIndependent(signupTeamConfirm)).Methods("GET")
- mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
- mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
- mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
- mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") // Remove after a few releases (~1.8)
- mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") // Remove after a few releases (~1.8)
- mainrouter.Handle("/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET")
-
- mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
- mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
- mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}", api.UserRequired(adminConsole)).Methods("GET")
- mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}/{team:[A-Za-z0-9-]*}", api.UserRequired(adminConsole)).Methods("GET")
-
- mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
-
- mainrouter.Handle("/docs/{doc:[A-Za-z0-9]+}", api.AppHandlerIndependent(docs)).Methods("GET")
-
- // ----------------------------------------------------------------------------------------------
- // *ANYTHING* team specific should go below this line
- // ----------------------------------------------------------------------------------------------
-
- mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.AppHandler(login)).Methods("GET")
- mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/", api.AppHandler(login)).Methods("GET")
- mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
- mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
- mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
- mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/claim", api.AppHandler(claimAccount)).Methods("GET")
- mainrouter.Handle("/{team}/pl/{postid}", api.AppHandler(postPermalink)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
- mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
- mainrouter.Handle("/{team}/channels/{channelname}", api.AppHandler(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
- mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
-
- watchAndParseTemplates()
-}
-
-func watchAndParseTemplates() {
-
- templatesDir := utils.FindDir("web/templates")
- l4g.Debug(utils.T("web.parsing_templates.debug"), templatesDir)
- var err error
- if Templates, err = template.ParseGlob(templatesDir + "*.html"); err != nil {
- l4g.Error(utils.T("web.parsing_templates.error"), err)
- }
-
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- l4g.Error(utils.T("web.create_dir.error"), err)
- }
-
- go func() {
- for {
- select {
- case event := <-watcher.Events:
- if event.Op&fsnotify.Write == fsnotify.Write {
- l4g.Info(utils.T("web.reparse_templates.info"), event.Name)
- if Templates, err = template.ParseGlob(templatesDir + "*.html"); err != nil {
- l4g.Error(utils.T("web.parsing_templates.error"), err)
- }
- }
- case err := <-watcher.Errors:
- l4g.Error(utils.T("web.dir_fail.error"), err)
- }
- }
- }()
-
- err = watcher.Add(templatesDir)
- if err != nil {
- l4g.Error(utils.T("web.watcher_fail.error"), err)
- }
+ mainrouter.Handle("/{anything:.*}", api.AppHandlerIndependent(root)).Methods("GET")
}
var browsersNotSupported string = "MSIE/8;MSIE/9;MSIE/10;Internet Explorer/8;Internet Explorer/9;Internet Explorer/10;Safari/7;Safari/8"
@@ -177,1026 +52,9 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- if len(c.Session.UserId) == 0 {
- page := NewHtmlTemplatePage("signup_team", c.T("web.root.singup_title"), c.Locale)
- page.Props["Info"] = c.T("web.root.singup_info")
-
- if result := <-api.Srv.Store.Team().GetAllTeamListing(); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- teams := result.Data.([]*model.Team)
- for _, team := range teams {
- page.Props[team.Name] = team.DisplayName
- }
-
- if len(teams) == 1 && *utils.Cfg.TeamSettings.EnableTeamListing && !utils.Cfg.TeamSettings.EnableTeamCreation {
- http.Redirect(w, r, c.GetSiteURL()+"/"+teams[0].Name, http.StatusTemporaryRedirect)
- return
- }
- }
-
- page.Render(c, w)
- } else {
- teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
- userChan := api.Srv.Store.User().Get(c.Session.UserId)
-
- var team *model.Team
- if tr := <-teamChan; tr.Err != nil {
- c.Err = tr.Err
- return
- } else {
- team = tr.Data.(*model.Team)
-
- }
-
- var user *model.User
- if ur := <-userChan; ur.Err != nil {
- c.Err = ur.Err
- return
- } else {
- user = ur.Data.(*model.User)
- }
-
- page := NewHtmlTemplatePage("home", c.T("web.root.home_title"), c.Locale)
- page.Team = team
- page.User = user
- page.Render(c, w)
- }
-}
-
-func signup(c *api.Context, w http.ResponseWriter, r *http.Request) {
-
- if !CheckBrowserCompatability(c, r) {
- return
- }
-
- page := NewHtmlTemplatePage("signup_team", c.T("web.root.singup_title"), c.Locale)
- page.Render(c, w)
-}
-
-func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
- if !CheckBrowserCompatability(c, r) {
- return
- }
- params := mux.Vars(r)
- teamName := params["team"]
-
- var team *model.Team
- if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
- l4g.Error(utils.T("web.login.error"), teamName, tResult.Err.Message)
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
- return
- } else {
- team = tResult.Data.(*model.Team)
- }
-
- // We still might be able to switch to this team because we've logged in before
- _, session := api.FindMultiSessionForTeamId(r, team.Id)
- if session != nil {
- w.Header().Set(model.HEADER_TOKEN, session.Token)
- lastViewChannelName := "town-square"
- if lastViewResult := <-api.Srv.Store.Preference().Get(session.UserId, model.PREFERENCE_CATEGORY_LAST, model.PREFERENCE_NAME_LAST_CHANNEL); lastViewResult.Err == nil {
- if lastViewChannelResult := <-api.Srv.Store.Channel().Get(lastViewResult.Data.(model.Preference).Value); lastViewChannelResult.Err == nil {
- lastViewChannelName = lastViewChannelResult.Data.(*model.Channel).Name
- }
- }
-
- http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/"+lastViewChannelName, http.StatusTemporaryRedirect)
- return
- }
-
- page := NewHtmlTemplatePage("login", c.T("web.login.login_title"), c.Locale)
- page.Props["TeamDisplayName"] = team.DisplayName
- page.Props["TeamName"] = team.Name
-
- if team.AllowOpenInvite {
- page.Props["InviteId"] = team.InviteId
- }
-
- page.Render(c, w)
-}
-
-func signupTeamConfirm(c *api.Context, w http.ResponseWriter, r *http.Request) {
- email := r.FormValue("email")
-
- page := NewHtmlTemplatePage("signup_team_confirm", c.T("web.signup_team_confirm.title"), c.Locale)
- page.Props["Email"] = email
- page.Render(c, w)
-}
-
-func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request) {
- data := r.FormValue("d")
- hash := r.FormValue("h")
-
- if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_team_complete.invalid_link.app_error", nil, "")
- return
- }
-
- props := model.MapFromJson(strings.NewReader(data))
-
- t, err := strconv.ParseInt(props["time"], 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60*24*30 { // 30 days
- c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_team_complete.link_expired.app_error", nil, "")
- return
- }
-
- page := NewHtmlTemplatePage("signup_team_complete", c.T("web.signup_team_complete.title"), c.Locale)
- page.Props["Email"] = props["email"]
- page.Props["Data"] = data
- page.Props["Hash"] = hash
- page.Render(c, w)
-}
-
-func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request) {
-
- id := r.FormValue("id")
- data := r.FormValue("d")
- hash := r.FormValue("h")
- var props map[string]string
-
- if len(id) > 0 {
- props = make(map[string]string)
-
- if result := <-api.Srv.Store.Team().GetByInviteId(id); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team := result.Data.(*model.Team)
- if !(team.Type == model.TEAM_OPEN || (team.Type == model.TEAM_INVITE && len(team.AllowedDomains) > 0)) {
- c.Err = model.NewLocAppError("signupUserComplete", "web.signup_user_complete.no_invites.app_error", nil, "id="+id)
- return
- }
-
- props["email"] = ""
- props["display_name"] = team.DisplayName
- props["name"] = team.Name
- props["id"] = team.Id
- data = model.MapToJson(props)
- hash = ""
- }
- } else {
-
- if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_user_complete.link_invalid.app_error", nil, "")
- return
- }
-
- props = model.MapFromJson(strings.NewReader(data))
-
- t, err := strconv.ParseInt(props["time"], 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hour
- c.Err = model.NewLocAppError("signupTeamComplete", "web.signup_user_complete.link_expired.app_error", nil, "")
- return
- }
- }
-
- page := NewHtmlTemplatePage("signup_user_complete", c.T("web.signup_user_complete.title"), c.Locale)
- page.Props["Email"] = props["email"]
- page.Props["TeamDisplayName"] = props["display_name"]
- page.Props["TeamName"] = props["name"]
- page.Props["TeamId"] = props["id"]
- page.Props["Data"] = data
- page.Props["Hash"] = hash
- page.Render(c, w)
-}
-
-func logout(c *api.Context, w http.ResponseWriter, r *http.Request) {
- api.Logout(c, w, r)
- http.Redirect(w, r, c.GetTeamURL(), http.StatusTemporaryRedirect)
-}
-
-func postPermalink(c *api.Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- teamName := params["team"]
- postId := params["postid"]
-
- if len(postId) != 26 {
- c.Err = model.NewLocAppError("postPermalink", "web.post_permalink.app_error", nil, "id="+postId)
- return
- }
-
- team := checkSessionSwitch(c, w, r, teamName)
- if team == nil {
- // Error already set by getTeam
- return
- }
-
- var post *model.Post
- if result := <-api.Srv.Store.Post().Get(postId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- postlist := result.Data.(*model.PostList)
- post = postlist.Posts[postlist.Order[0]]
- }
-
- var channel *model.Channel
- if result := <-api.Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, post.ChannelId, c.Session.UserId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- if result.Data.(int64) == 0 {
- if channel = autoJoinChannelId(c, w, r, post.ChannelId); channel == nil {
- http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
- return
- }
- } else {
- if result := <-api.Srv.Store.Channel().Get(post.ChannelId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- channel = result.Data.(*model.Channel)
- }
- }
- }
-
- doLoadChannel(c, w, r, team, channel, post.Id)
-}
-
-func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- name := params["channelname"]
- teamName := params["team"]
-
- team := checkSessionSwitch(c, w, r, teamName)
- if team == nil {
- // Error already set by getTeam
- return
- }
-
- var channel *model.Channel
- if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- channelId := result.Data.(string)
- if len(channelId) == 0 {
- if channel = autoJoinChannelName(c, w, r, name); channel == nil {
- http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
- return
- }
- } else {
- if result := <-api.Srv.Store.Channel().Get(channelId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- channel = result.Data.(*model.Channel)
- }
- }
- }
-
- doLoadChannel(c, w, r, team, channel, "")
-}
-
-func autoJoinChannelName(c *api.Context, w http.ResponseWriter, r *http.Request, channelName string) *model.Channel {
- if strings.Index(channelName, "__") > 0 {
- // It's a direct message channel that doesn't exist yet so let's create it
- ids := strings.Split(channelName, "__")
- otherUserId := ""
- if ids[0] == c.Session.UserId {
- otherUserId = ids[1]
- } else {
- otherUserId = ids[0]
- }
-
- if sc, err := api.CreateDirectChannel(c, otherUserId); err != nil {
- api.Handle404(w, r)
- return nil
- } else {
- return sc
- }
- } else {
- // We will attempt to auto-join open channels
- return joinOpenChannel(c, w, r, api.Srv.Store.Channel().GetByName(c.Session.TeamId, channelName))
- }
-
- return nil
-}
-
-func autoJoinChannelId(c *api.Context, w http.ResponseWriter, r *http.Request, channelId string) *model.Channel {
- return joinOpenChannel(c, w, r, api.Srv.Store.Channel().Get(channelId))
-}
-
-func joinOpenChannel(c *api.Context, w http.ResponseWriter, r *http.Request, channel store.StoreChannel) *model.Channel {
- if cr := <-channel; cr.Err != nil {
- http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
- return nil
- } else {
- channel := cr.Data.(*model.Channel)
- if channel.Type == model.CHANNEL_OPEN {
- api.JoinChannel(c, channel.Id, "")
- if c.Err != nil {
- return nil
- }
- } else {
- http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
- return nil
- }
- return channel
- }
-}
-
-func checkSessionSwitch(c *api.Context, w http.ResponseWriter, r *http.Request, teamName string) *model.Team {
- var team *model.Team
- if result := <-api.Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return nil
- } else {
- team = result.Data.(*model.Team)
- }
-
- // We are logged into a different team. Lets see if we have another
- // session in the cookie that will give us access.
- if c.Session.TeamId != team.Id {
- index, session := api.FindMultiSessionForTeamId(r, team.Id)
- if session == nil {
- // redirect to login
- http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
- } else {
- c.Session = *session
- c.SessionTokenIndex = index
- }
- }
-
- return team
-}
-
-func doLoadChannel(c *api.Context, w http.ResponseWriter, r *http.Request, team *model.Team, channel *model.Channel, postid string) {
- userChan := api.Srv.Store.User().Get(c.Session.UserId)
- prefChan := api.Srv.Store.Preference().GetAll(c.Session.UserId)
-
- var user *model.User
- if ur := <-userChan; ur.Err != nil {
- c.Err = ur.Err
- c.RemoveSessionCookie(w, r)
- l4g.Error(utils.T("web.do_load_channel.error"), c.Session.UserId)
- return
- } else {
- user = ur.Data.(*model.User)
- }
-
- var preferences model.Preferences
- if result := <-prefChan; result.Err != nil {
- l4g.Error("Error in getting preferences for id=%v", c.Session.UserId)
- } else {
- preferences = result.Data.(model.Preferences)
- }
-
- page := NewHtmlTemplatePage("channel", "", c.Locale)
- page.Props["Title"] = channel.DisplayName + " - " + team.DisplayName + " " + page.ClientCfg["SiteName"]
- page.Props["TeamDisplayName"] = team.DisplayName
- page.Props["ChannelName"] = channel.Name
- page.Props["ChannelId"] = channel.Id
- page.Props["PostId"] = postid
- page.Team = team
- page.User = user
- page.Channel = channel
- page.Preferences = &preferences
- page.Render(c, w)
-}
-
-func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
- resend := r.URL.Query().Get("resend")
- resendSuccess := r.URL.Query().Get("resend_success")
- name := r.URL.Query().Get("teamname")
- email := r.URL.Query().Get("email")
- hashedId := r.URL.Query().Get("hid")
- userId := r.URL.Query().Get("uid")
-
- var team *model.Team
- if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
- if resend == "true" {
- if result := <-api.Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- user := result.Data.(*model.User)
-
- if user.LastActivityAt > 0 {
- api.SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
- } else {
- api.SendVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
- }
-
- newAddress := strings.Replace(r.URL.String(), "&resend=true", "&resend_success=true", -1)
- http.Redirect(w, r, newAddress, http.StatusFound)
- return
- }
- }
-
- if len(userId) == 26 && len(hashedId) != 0 && model.ComparePassword(hashedId, userId) {
- if c.Err = (<-api.Srv.Store.User().VerifyEmail(userId)).Err; c.Err != nil {
- return
- } else {
- c.LogAudit("Email Verified")
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?extra=verified&email="+url.QueryEscape(email), http.StatusTemporaryRedirect)
- return
- }
- }
-
- page := NewHtmlTemplatePage("verify", c.T("web.email_verified.title"), c.Locale)
- page.Props["TeamURL"] = c.GetTeamURLFromTeam(team)
- page.Props["UserEmail"] = email
- page.Props["ResendSuccess"] = resendSuccess
- page.Render(c, w)
-}
-
-func findTeam(c *api.Context, w http.ResponseWriter, r *http.Request) {
- page := NewHtmlTemplatePage("find_team", c.T("web.find_team.title"), c.Locale)
- page.Render(c, w)
-}
-
-func docs(c *api.Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- doc := params["doc"]
-
- var user *model.User
- if len(c.Session.UserId) != 0 {
- userChan := api.Srv.Store.User().Get(c.Session.UserId)
- if userChan := <-userChan; userChan.Err == nil {
- user = userChan.Data.(*model.User)
- }
- }
-
- page := NewHtmlTemplatePage("docs", c.T("web.doc.title"), c.Locale)
- page.Props["Site"] = doc
- page.User = user
- page.Render(c, w)
-}
-
-func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
- isResetLink := true
- hash := r.URL.Query().Get("h")
- data := r.URL.Query().Get("d")
- params := mux.Vars(r)
- teamName := params["team"]
-
- if len(hash) == 0 || len(data) == 0 {
- isResetLink = false
- } else {
- if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt)) {
- c.Err = model.NewLocAppError("resetPassword", "web.reset_password.invalid_link.app_error", nil, "")
- return
- }
-
- props := model.MapFromJson(strings.NewReader(data))
-
- t, err := strconv.ParseInt(props["time"], 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour
- c.Err = model.NewLocAppError("resetPassword", "web.reset_password.expired_link.app_error", nil, "")
- return
- }
- }
-
- teamDisplayName := "Developer/Beta"
- var team *model.Team
- if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
- c.Err = tResult.Err
- return
- } else {
- team = tResult.Data.(*model.Team)
- }
-
- if team != nil {
- teamDisplayName = team.DisplayName
- }
-
- page := NewHtmlTemplatePage("password_reset", "", c.Locale)
- page.Props["Title"] = "Reset Password " + page.ClientCfg["SiteName"]
- page.Props["TeamDisplayName"] = teamDisplayName
- page.Props["TeamName"] = teamName
- page.Props["Hash"] = hash
- page.Props["Data"] = data
- page.Props["TeamName"] = teamName
- page.Props["IsReset"] = strconv.FormatBool(isResetLink)
- page.Render(c, w)
-}
-
-func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- service := params["service"]
- teamName := params["team"]
-
- if !utils.Cfg.TeamSettings.EnableUserCreation {
- c.Err = model.NewLocAppError("signupTeam", "web.singup_with_oauth.disabled.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- if len(teamName) == 0 {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
- c.Err.StatusCode = http.StatusBadRequest
- return
- }
-
- hash := r.URL.Query().Get("h")
-
- var team *model.Team
- if result := <-api.Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
- }
-
- if api.IsVerifyHashRequired(nil, team, hash) {
- data := r.URL.Query().Get("d")
- props := model.MapFromJson(strings.NewReader(data))
-
- if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_link.app_error", nil, "")
- return
- }
-
- t, err := strconv.ParseInt(props["time"], 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.expired_link.app_error", nil, "")
- return
- }
-
- if team.Id != props["id"] {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, data)
- return
- }
- }
-
- stateProps := map[string]string{}
- stateProps["action"] = model.OAUTH_ACTION_SIGNUP
-
- if authUrl, err := api.GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
- c.Err = err
- return
- } else {
- http.Redirect(w, r, authUrl, http.StatusFound)
- }
-}
-
-func completeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- service := params["service"]
-
- code := r.URL.Query().Get("code")
- state := r.URL.Query().Get("state")
-
- uri := c.GetSiteURL() + "/signup/" + service + "/complete" // Remove /signup after a few releases (~1.8)
-
- if body, team, props, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
- c.Err = err
- return
- } else {
- action := props["action"]
- switch action {
- case model.OAUTH_ACTION_SIGNUP:
- api.CreateOAuthUser(c, w, r, service, body, team)
- if c.Err == nil {
- root(c, w, r)
- }
- break
- case model.OAUTH_ACTION_LOGIN:
- api.LoginByOAuth(c, w, r, service, body, team)
- if c.Err == nil {
- root(c, w, r)
- }
- break
- case model.OAUTH_ACTION_EMAIL_TO_SSO:
- api.CompleteSwitchWithOAuth(c, w, r, service, body, team, props["email"])
- if c.Err == nil {
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/login?extra=signin_change", http.StatusTemporaryRedirect)
- }
- break
- case model.OAUTH_ACTION_SSO_TO_EMAIL:
- api.LoginByOAuth(c, w, r, service, body, team)
- if c.Err == nil {
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/"+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
- }
- break
- default:
- api.LoginByOAuth(c, w, r, service, body, team)
- if c.Err == nil {
- root(c, w, r)
- }
- break
- }
- }
-}
-
-func loginWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- service := params["service"]
- teamName := params["team"]
- loginHint := r.URL.Query().Get("login_hint")
-
- if len(teamName) == 0 {
- c.Err = model.NewLocAppError("loginWithOAuth", "web.login_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
- c.Err.StatusCode = http.StatusBadRequest
- return
- }
-
- // Make sure team exists
- if result := <-api.Srv.Store.Team().GetByName(teamName); result.Err != nil {
- c.Err = result.Err
- return
- }
-
- stateProps := map[string]string{}
- stateProps["action"] = model.OAUTH_ACTION_LOGIN
-
- if authUrl, err := api.GetAuthorizationCode(c, service, teamName, stateProps, loginHint); err != nil {
- c.Err = err
- return
- } else {
- http.Redirect(w, r, authUrl, http.StatusFound)
- }
-}
-
-func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
-
- if !c.HasSystemAdminPermissions("adminConsole") {
- return
- }
-
- teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
- userChan := api.Srv.Store.User().Get(c.Session.UserId)
-
- var team *model.Team
- if tr := <-teamChan; tr.Err != nil {
- c.Err = tr.Err
- return
- } else {
- team = tr.Data.(*model.Team)
-
- }
-
- var user *model.User
- if ur := <-userChan; ur.Err != nil {
- c.Err = ur.Err
- return
- } else {
- user = ur.Data.(*model.User)
- }
-
- params := mux.Vars(r)
- activeTab := params["tab"]
- teamId := params["team"]
-
- page := NewHtmlTemplatePage("admin_console", c.T("web.admin_console.title"), c.Locale)
- page.User = user
- page.Team = team
- page.Props["ActiveTab"] = activeTab
- page.Props["TeamId"] = teamId
- page.Render(c, w)
-}
-
-func authorizeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
- if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
- c.Err = model.NewLocAppError("authorizeOAuth", "web.authorize_oauth.disabled.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- if !CheckBrowserCompatability(c, r) {
- return
- }
-
- responseType := r.URL.Query().Get("response_type")
- clientId := r.URL.Query().Get("client_id")
- redirect := r.URL.Query().Get("redirect_uri")
- scope := r.URL.Query().Get("scope")
- state := r.URL.Query().Get("state")
-
- if len(responseType) == 0 || len(clientId) == 0 || len(redirect) == 0 {
- c.Err = model.NewLocAppError("authorizeOAuth", "web.authorize_oauth.missing.app_error", nil, "")
- return
- }
-
- var app *model.OAuthApp
- if result := <-api.Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- app = result.Data.(*model.OAuthApp)
- }
-
- var team *model.Team
- if result := <-api.Srv.Store.Team().Get(c.Session.TeamId); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- team = result.Data.(*model.Team)
+ page := utils.NewHTMLTemplate("root", c.Locale)
+ page.Props["Title"] = c.T("web.root.home_title")
+ if err := page.RenderToWriter(w); err != nil {
+ c.SetUnknownError(page.TemplateName, err.Error())
}
-
- page := NewHtmlTemplatePage("authorize", c.T("web.authorize_oauth.title"), c.Locale)
- page.Props["TeamName"] = team.Name
- page.Props["AppName"] = app.Name
- page.Props["ResponseType"] = responseType
- page.Props["ClientId"] = clientId
- page.Props["RedirectUri"] = redirect
- page.Props["Scope"] = scope
- page.Props["State"] = state
- page.Render(c, w)
-}
-
-func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
- if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.disabled.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- c.LogAudit("attempt")
-
- r.ParseForm()
-
- grantType := r.FormValue("grant_type")
- if grantType != model.ACCESS_TOKEN_GRANT_TYPE {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_grant.app_error", nil, "")
- return
- }
-
- clientId := r.FormValue("client_id")
- if len(clientId) != 26 {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_client_id.app_error", nil, "")
- return
- }
-
- secret := r.FormValue("client_secret")
- if len(secret) == 0 {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_client_secret.app_error", nil, "")
- return
- }
-
- code := r.FormValue("code")
- if len(code) == 0 {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.missing_code.app_error", nil, "")
- return
- }
-
- redirectUri := r.FormValue("redirect_uri")
-
- achan := api.Srv.Store.OAuth().GetApp(clientId)
- tchan := api.Srv.Store.OAuth().GetAccessDataByAuthCode(code)
-
- authData := api.GetAuthData(code)
-
- if authData == nil {
- c.LogAudit("fail - invalid auth code")
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
- return
- }
-
- uchan := api.Srv.Store.User().Get(authData.UserId)
-
- if authData.IsExpired() {
- c.LogAudit("fail - auth code expired")
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
- return
- }
-
- if authData.RedirectUri != redirectUri {
- c.LogAudit("fail - redirect uri provided did not match previous redirect uri")
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.redirect_uri.app_error", nil, "")
- return
- }
-
- if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) {
- c.LogAudit("fail - auth code is invalid")
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
- return
- }
-
- var app *model.OAuthApp
- if result := <-achan; result.Err != nil {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.credentials.app_error", nil, "")
- return
- } else {
- app = result.Data.(*model.OAuthApp)
- }
-
- if !model.ComparePassword(app.ClientSecret, secret) {
- c.LogAudit("fail - invalid client credentials")
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.credentials.app_error", nil, "")
- return
- }
-
- callback := redirectUri
- if len(callback) == 0 {
- callback = app.CallbackUrls[0]
- }
-
- if result := <-tchan; result.Err != nil {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal.app_error", nil, "")
- return
- } else if result.Data != nil {
- c.LogAudit("fail - auth code has been used previously")
- accessData := result.Data.(*model.AccessData)
-
- // Revoke access token, related auth code, and session from DB as well as from cache
- if err := api.RevokeAccessToken(accessData.Token); err != nil {
- l4g.Error(utils.T("web.get_access_token.revoking.error") + err.Message)
- }
-
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.exchanged.app_error", nil, "")
- return
- }
-
- var user *model.User
- if result := <-uchan; result.Err != nil {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_user.app_error", nil, "")
- return
- } else {
- user = result.Data.(*model.User)
- }
-
- session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, IsOAuth: true}
-
- if result := <-api.Srv.Store.Session().Save(session); result.Err != nil {
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_session.app_error", nil, "")
- return
- } else {
- session = result.Data.(*model.Session)
- api.AddSessionToCache(session)
- }
-
- accessData := &model.AccessData{AuthCode: authData.Code, Token: session.Token, RedirectUri: callback}
-
- if result := <-api.Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil {
- l4g.Error(result.Err)
- c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_saving.app_error", nil, "")
- return
- }
-
- accessRsp := &model.AccessResponse{AccessToken: session.Token, TokenType: model.ACCESS_TOKEN_TYPE, ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24)}
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Cache-Control", "no-store")
- w.Header().Set("Pragma", "no-cache")
-
- c.LogAuditWithUserId(user.Id, "success")
-
- w.Write([]byte(accessRsp.ToJson()))
-}
-
-func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
- if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.disabled.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- params := mux.Vars(r)
- id := params["id"]
-
- hchan := api.Srv.Store.Webhook().GetIncoming(id)
-
- r.ParseForm()
-
- var parsedRequest *model.IncomingWebhookRequest
- contentType := r.Header.Get("Content-Type")
- if strings.Split(contentType, "; ")[0] == "application/json" {
- parsedRequest = model.IncomingWebhookRequestFromJson(r.Body)
- } else {
- parsedRequest = model.IncomingWebhookRequestFromJson(strings.NewReader(r.FormValue("payload")))
- }
-
- if parsedRequest == nil {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.parse.app_error", nil, "")
- return
- }
-
- text := parsedRequest.Text
- if len(text) == 0 && parsedRequest.Attachments == nil {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.text.app_error", nil, "")
- return
- }
-
- channelName := parsedRequest.ChannelName
- webhookType := parsedRequest.Type
-
- //attachments is in here for slack compatibility
- if parsedRequest.Attachments != nil {
- if len(parsedRequest.Props) == 0 {
- parsedRequest.Props = make(model.StringInterface)
- }
- parsedRequest.Props["attachments"] = parsedRequest.Attachments
- webhookType = model.POST_SLACK_ATTACHMENT
- }
-
- var hook *model.IncomingWebhook
- if result := <-hchan; result.Err != nil {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.invalid.app_error", nil, "err="+result.Err.Message)
- return
- } else {
- hook = result.Data.(*model.IncomingWebhook)
- }
-
- var channel *model.Channel
- var cchan store.StoreChannel
-
- if len(channelName) != 0 {
- if channelName[0] == '@' {
- if result := <-api.Srv.Store.User().GetByUsername(hook.TeamId, channelName[1:]); result.Err != nil {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.user.app_error", nil, "err="+result.Err.Message)
- return
- } else {
- channelName = model.GetDMNameFromIds(result.Data.(*model.User).Id, hook.UserId)
- }
- } else if channelName[0] == '#' {
- channelName = channelName[1:]
- }
-
- cchan = api.Srv.Store.Channel().GetByName(hook.TeamId, channelName)
- } else {
- cchan = api.Srv.Store.Channel().Get(hook.ChannelId)
- }
-
- overrideUsername := parsedRequest.Username
- overrideIconUrl := parsedRequest.IconURL
-
- if result := <-cchan; result.Err != nil {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.channel.app_error", nil, "err="+result.Err.Message)
- return
- } else {
- channel = result.Data.(*model.Channel)
- }
-
- pchan := api.Srv.Store.Channel().CheckPermissionsTo(hook.TeamId, channel.Id, hook.UserId)
-
- // create a mock session
- c.Session = model.Session{UserId: hook.UserId, TeamId: hook.TeamId, IsOAuth: false}
-
- if !c.HasPermissionsToChannel(pchan, "createIncomingHook") && channel.Type != model.CHANNEL_OPEN {
- c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.permissions.app_error", nil, "")
- return
- }
-
- if _, err := api.CreateWebhookPost(c, channel.Id, text, overrideUsername, overrideIconUrl, parsedRequest.Props, webhookType); err != nil {
- c.Err = err
- return
- }
-
- w.Header().Set("Content-Type", "text/plain")
- w.Write([]byte("ok"))
-}
-
-func claimAccount(c *api.Context, w http.ResponseWriter, r *http.Request) {
- if !CheckBrowserCompatability(c, r) {
- return
- }
-
- params := mux.Vars(r)
- teamName := params["team"]
- email := r.URL.Query().Get("email")
- newType := r.URL.Query().Get("new_type")
-
- var team *model.Team
- if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
- l4g.Error(utils.T("web.claim_account.team.error"), teamName, tResult.Err.Message)
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
- return
- } else {
- team = tResult.Data.(*model.Team)
- }
-
- authType := ""
- if len(email) != 0 {
- if uResult := <-api.Srv.Store.User().GetByEmail(team.Id, email); uResult.Err != nil {
- l4g.Error(utils.T("web.claim_account.user.error"), team.Id, email, uResult.Err.Message)
- http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
- return
- } else {
- user := uResult.Data.(*model.User)
- authType = user.AuthService
-
- // if user is not logged in to their SSO account, ask them to log in
- if len(authType) != 0 && user.Id != c.Session.UserId {
- stateProps := map[string]string{}
- stateProps["action"] = model.OAUTH_ACTION_SSO_TO_EMAIL
- stateProps["email"] = email
-
- if authUrl, err := api.GetAuthorizationCode(c, authType, team.Name, stateProps, ""); err != nil {
- c.Err = err
- return
- } else {
- http.Redirect(w, r, authUrl, http.StatusFound)
- }
- }
- }
- }
-
- page := NewHtmlTemplatePage("claim_account", c.T("web.claim_account.title"), c.Locale)
- page.Props["Email"] = email
- page.Props["CurrentType"] = authType
- page.Props["NewType"] = newType
- page.Props["TeamDisplayName"] = team.DisplayName
- page.Props["TeamName"] = team.Name
-
- page.Render(c, w)
}