Convering client to react-router.

Этот коммит содержится в:
Christopher Speller
2016-02-08 07:26:10 -05:00
родитель 08f0800ade
Коммит d7cdcf082f
183 изменённых файлов: 3433 добавлений и 5091 удалений

Просмотреть файл

@@ -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();
}

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -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'

Просмотреть файл

@@ -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>
);
}
}
}

Просмотреть файл

@@ -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();

Просмотреть файл

@@ -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>
);
}
}
}

Просмотреть файл

@@ -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'
/>

Просмотреть файл

@@ -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;

Просмотреть файл

@@ -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()))
};
}
validState() {
return this.state.user && this.state.channel && this.state.profiles;
}
onStoresChange() {
this.setState(this.getStateFromStores());
}
componentDidMount() {
PreferenceStore.addChangeListener(this.onPreferenceChange);
ChannelStore.addChangeListener(this.onChannelChange);
UserStore.addChangeListener(this.onUserChange);
PreferenceStore.addChangeListener(this.onStoresChange);
ChannelStore.addChangeListener(this.onStoresChange);
UserStore.addChangeListener(this.onStoresChange);
}
componentWillUnmount() {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
ChannelStore.removeChangeListener(this.onChannelChange);
UserStore.removeChangeListener(this.onUserChange);
}
onPreferenceChange() {
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
this.setState({showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS});
}
onChannelChange() {
this.setState({showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS});
}
onUserChange() {
this.setState({user: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
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;

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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}}

Просмотреть файл

@@ -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);

Просмотреть файл

@@ -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: ''
};
}
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
this.onListenerChange();
ChannelStore.addChangeListener(this.onListenerChange);
} else {
ChannelStore.removeChangeListener(this.onListenerChange);
}
}
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});
}
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
this.setState({
notifyLevel: nextProps.channelMember.notify_props.desktop,
unreadLevel: nextProps.channelMember.notify_props.mark_unread
});
}
}
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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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);

Просмотреть файл

@@ -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') {

Просмотреть файл

@@ -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');

82
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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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;
}

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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) {

Просмотреть файл

@@ -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);

Просмотреть файл

@@ -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() {

224
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
};

Просмотреть файл

@@ -6,82 +6,118 @@ 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;
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'
/>
);
} 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.changed'
defaultMessage=' Sign-in method changed successfully'
/>
</div>
);
} else if (extraParam === Constants.SIGNIN_VERIFIED) {
extraBox = (
<div className='alert alert-success'>
<i className='fa fa-check'/>
<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 +127,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 +161,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 +170,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,73 +204,65 @@ 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>
</div>
);
}
let usernameLogin = null;
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
}}
/>
</h2>
{extraBox}
{loginMessage}
{emailSignup}
{usernameLogin}
{ldapLogin}
{userSignUp}
{findTeams}
{forgotPassword}
{teamSignUp}
<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
};

Просмотреть файл

@@ -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);
export default injectIntl(LoginEmail);

Просмотреть файл

@@ -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;

Просмотреть файл

@@ -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
};

20
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
};

70
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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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);
export default PasswordResetForm;

Просмотреть файл

@@ -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);
export default PasswordResetSendLink;

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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';
@@ -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}
@@ -245,5 +246,7 @@ Post.propTypes = {
hideProfilePic: React.PropTypes.bool,
isLastComment: React.PropTypes.bool,
shouldHighlight: React.PropTypes.bool,
displayNameType: React.PropTypes.string
displayNameType: React.PropTypes.string,
hasProfiles: React.PropTypes.bool,
currentUser: React.PropTypes.object.isRequired
};

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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>
);

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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,9 +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}
user={profile}
currentUser={this.props.currentUser}
/>
);
@@ -525,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,
@@ -535,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}) {

Просмотреть файл

@@ -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) {
@@ -165,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);
@@ -185,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) {

Просмотреть файл

@@ -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'
/>

Просмотреть файл

@@ -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;

90
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
};

Просмотреть файл

@@ -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'
/>

111
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
};

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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>
);
}

Просмотреть файл

@@ -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}

Просмотреть файл

@@ -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>
} else if (this.state.page === 'choose') {
signupMethod = (
<ChoosePage
updatePage={this.updatePage}
/>
);
}
if (this.state.page === 'choose') {
return (
<div>
{teamListing}
<ChoosePage
updatePage={this.updatePage}
/>
</div>
);
}
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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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);
export default injectIntl(TeamSignupDisplayNamePage);

Просмотреть файл

@@ -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';

Просмотреть файл

@@ -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."
/>
);
}
}

Просмотреть файл

@@ -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);
export default injectIntl(TeamSignupPasswordPage);

Просмотреть файл

@@ -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';

Просмотреть файл

@@ -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);
export default injectIntl(TeamSignupUrlPage);

Просмотреть файл

@@ -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);
export default injectIntl(TeamSignupUsernamePage);

Просмотреть файл

@@ -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);
export default injectIntl(TeamSignupWelcomePage);

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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);
var initialState = BrowserStore.getGlobalItem(this.props.hash);
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 (!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;
// 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 = '';
}
this.state = initialState;
this.setState({
data,
hash,
usedBefore,
email,
teamDisplayName,
teamName,
teamId
});
}
inviteInfoRecieved(data) {
if (!data) {
return;
}
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,
() => {
client.track('signup', 'signup_user_02_complete');
const user = {
team_id: this.state.teamId,
email: providedEmail,
username: providedUsername,
password: providedPassword,
allow_marketing: true
};
client.loginByEmail(this.props.teamName, user.email, user.password,
Client.createUser(user, this.state.data, this.state.hash,
() => {
Client.track('signup', 'signup_user_02_complete');
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);
export default SignupUserComplete;

Просмотреть файл

@@ -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}
/>
);
}

Просмотреть файл

@@ -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);

Просмотреть файл

@@ -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();
}

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -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':

Просмотреть файл

@@ -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);
export default injectIntl(EmailSignUpPage);

Просмотреть файл

@@ -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'

Просмотреть файл

@@ -26,22 +26,27 @@ export default class UserProfile extends React.Component {
}
}
render() {
var name = Utils.displayUsername(this.props.user.id);
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.overwriteName) {
name = this.props.overwriteName;
} else if (!name) {
name = '...';
}
if (this.props.overwriteImage) {
profileImg = this.props.overwriteImage;
}
if (this.props.disablePopover) {
return <div>{name}</div>;
}
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;
}
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

Просмотреть файл

@@ -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;

Просмотреть файл

@@ -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;

Просмотреть файл

@@ -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);
export default injectIntl(UserSettingsGeneralTab);

Просмотреть файл

@@ -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'});

Просмотреть файл

@@ -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);
export default injectIntl(SecurityTab);

Просмотреть файл

@@ -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": {

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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;

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

290
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);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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) {
@@ -156,7 +160,3 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
});
export default AdminStore;
if (window.mm_config.EnableDeveloper === 'true') {
window.AdminStore = AdminStore;
}

Просмотреть файл

@@ -83,7 +83,3 @@ AnalyticsStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default AnalyticsStore;
if (window.mm_config.EnableDeveloper === 'true') {
window.AnalyticsStore = AnalyticsStore;
}

Просмотреть файл

@@ -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();

Просмотреть файл

@@ -350,7 +350,3 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default ChannelStore;
if (window.mm_config.EnableDeveloper === 'true') {
window.ChannelStore = ChannelStore;
}

Просмотреть файл

@@ -57,9 +57,4 @@ class FileStore extends EventEmitter {
}
}
const instance = new FileStore();
export default instance;
if (window.mm_config.EnableDeveloper === 'true') {
window.FileStore = instance;
}
export default new FileStore();

60
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;

Просмотреть файл

@@ -45,7 +45,3 @@ class ModalStoreClass extends EventEmitter {
const ModalStore = new ModalStoreClass();
export default ModalStore;
if (window.mm_config.EnableDeveloper === 'true') {
window.ModalStore = ModalStore;
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше