Merging from master
Этот коммит содержится в:
@@ -81,6 +81,7 @@ export default class ActivityLogModal extends React.Component {
|
||||
const currentSession = this.state.sessions[i];
|
||||
const lastAccessTime = new Date(currentSession.last_activity_at);
|
||||
const firstAccessTime = new Date(currentSession.create_at);
|
||||
let devicePlatform = currentSession.props.platform;
|
||||
let devicePicture = '';
|
||||
|
||||
if (currentSession.props.platform === 'Windows') {
|
||||
@@ -88,7 +89,12 @@ export default class ActivityLogModal extends React.Component {
|
||||
} else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
|
||||
devicePicture = 'fa fa-apple';
|
||||
} else if (currentSession.props.platform === 'Linux') {
|
||||
devicePicture = 'fa fa-linux';
|
||||
if (currentSession.props.os.indexOf('Android') >= 0) {
|
||||
devicePlatform = 'Android';
|
||||
devicePicture = 'fa fa-android';
|
||||
} else {
|
||||
devicePicture = 'fa fa-linux';
|
||||
}
|
||||
}
|
||||
|
||||
let moreInfo;
|
||||
@@ -119,7 +125,7 @@ export default class ActivityLogModal extends React.Component {
|
||||
className='activity-log__table'
|
||||
>
|
||||
<div className='activity-log__report'>
|
||||
<div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
|
||||
<div className='report__platform'><i className={devicePicture} />{devicePlatform}</div>
|
||||
<div className='report__info'>
|
||||
<div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
|
||||
{moreInfo}
|
||||
|
||||
@@ -40,9 +40,13 @@ export default class AdminController extends React.Component {
|
||||
config: AdminStore.getConfig(),
|
||||
teams: AdminStore.getAllTeams(),
|
||||
selectedTeams,
|
||||
selected: 'service_settings',
|
||||
selectedTeam: null
|
||||
selected: props.tab || 'service_settings',
|
||||
selectedTeam: props.teamId || null
|
||||
};
|
||||
|
||||
if (!props.tab) {
|
||||
history.replaceState(null, null, `/admin_console/${this.state.selected}`);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -142,7 +146,9 @@ export default class AdminController extends React.Component {
|
||||
} else if (this.state.selected === 'service_settings') {
|
||||
tab = <ServiceSettingsTab config={this.state.config} />;
|
||||
} else if (this.state.selected === 'team_users') {
|
||||
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||
if (this.state.teams) {
|
||||
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export default class AdminSidebar extends React.Component {
|
||||
handleClick(name, teamId, e) {
|
||||
e.preventDefault();
|
||||
this.props.selectTab(name, teamId);
|
||||
history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
|
||||
}
|
||||
|
||||
isSelected(name, teamId) {
|
||||
@@ -53,6 +54,9 @@ export default class AdminSidebar extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if ($(window).width() > 768) {
|
||||
$('.nav-pills__container').perfectScrollbar();
|
||||
}
|
||||
}
|
||||
|
||||
showTeamSelect(e) {
|
||||
|
||||
@@ -440,9 +440,11 @@ export default class EmailSettings extends React.Component {
|
||||
className='table table-bordered'
|
||||
cellPadding='5'
|
||||
>
|
||||
<tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
|
||||
<tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
|
||||
<tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
|
||||
<tbody>
|
||||
<tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
|
||||
<tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
|
||||
<tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='help-text'>
|
||||
|
||||
@@ -249,22 +249,24 @@ export default class LogSettings extends React.Component {
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.fileEnable}
|
||||
/>
|
||||
<p className='help-text'>
|
||||
<div className='help-text'>
|
||||
{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'}
|
||||
<div className='help-text'>
|
||||
<table
|
||||
className='table table-bordered'
|
||||
cellPadding='5'
|
||||
>
|
||||
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
|
||||
<tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
|
||||
<tbody>
|
||||
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
|
||||
<tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
|
||||
<tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
const ChannelStore = require('../stores/channel_store.jsx');
|
||||
const UserStore = require('../stores/user_store.jsx');
|
||||
const PostStore = require('../stores/post_store.jsx');
|
||||
const SocketStore = require('../stores/socket_store.jsx');
|
||||
const NavbarSearchBox = require('./search_bar.jsx');
|
||||
const AsyncClient = require('../utils/async_client.jsx');
|
||||
const Client = require('../utils/client.jsx');
|
||||
@@ -25,7 +24,6 @@ export default class ChannelHeader extends React.Component {
|
||||
super(props);
|
||||
|
||||
this.onListenerChange = this.onListenerChange.bind(this);
|
||||
this.onSocketChange = this.onSocketChange.bind(this);
|
||||
this.handleLeave = this.handleLeave.bind(this);
|
||||
this.searchMentions = this.searchMentions.bind(this);
|
||||
|
||||
@@ -45,7 +43,6 @@ export default class ChannelHeader extends React.Component {
|
||||
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
||||
PostStore.addSearchChangeListener(this.onListenerChange);
|
||||
UserStore.addChangeListener(this.onListenerChange);
|
||||
SocketStore.addChangeListener(this.onSocketChange);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
||||
@@ -60,16 +57,9 @@ export default class ChannelHeader extends React.Component {
|
||||
}
|
||||
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
|
||||
}
|
||||
onSocketChange(msg) {
|
||||
if (msg.action === 'new_user' ||
|
||||
msg.action === 'user_added' ||
|
||||
(msg.action === 'user_removed' && msg.user_id !== UserStore.getCurrentId())) {
|
||||
AsyncClient.getChannelExtraInfo(true);
|
||||
}
|
||||
}
|
||||
handleLeave() {
|
||||
Client.leaveChannel(this.state.channel.id,
|
||||
function handleLeaveSuccess() {
|
||||
() => {
|
||||
AppDispatcher.handleViewAction({
|
||||
type: ActionTypes.LEAVE_CHANNEL,
|
||||
id: this.state.channel.id
|
||||
@@ -77,8 +67,8 @@ export default class ChannelHeader extends React.Component {
|
||||
|
||||
const townsquare = ChannelStore.getByName('town-square');
|
||||
Utils.switchChannel(townsquare);
|
||||
}.bind(this),
|
||||
function handleLeaveError(err) {
|
||||
},
|
||||
(err) => {
|
||||
AsyncClient.dispatchError(err, 'handleLeave');
|
||||
}
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class EditPostModal extends React.Component {
|
||||
refocusId: options.refocusId || ''
|
||||
});
|
||||
|
||||
$(React.findDOMNode(this.refs.modal)).modal('show');
|
||||
$(ReactDOM.findDOMNode(this.refs.modal)).modal('show');
|
||||
}
|
||||
componentDidMount() {
|
||||
var self = this;
|
||||
@@ -92,7 +92,7 @@ export default class EditPostModal extends React.Component {
|
||||
$('#edit_textbox').get(0).focus();
|
||||
});
|
||||
|
||||
$(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
|
||||
$(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
|
||||
if (self.state.refocusId !== '') {
|
||||
setTimeout(() => {
|
||||
$(self.state.refocusId).get(0).focus();
|
||||
|
||||
@@ -83,7 +83,7 @@ export default class MoreChannels extends React.Component {
|
||||
moreChannels = <LoadingScreen />;
|
||||
} else if (channels.length) {
|
||||
moreChannels = (
|
||||
<table className='more-channel-table table'>
|
||||
<table className='more-table table'>
|
||||
<tbody>
|
||||
{channels.map(function cMap(channel, index) {
|
||||
var joinButton;
|
||||
@@ -108,8 +108,8 @@ export default class MoreChannels extends React.Component {
|
||||
return (
|
||||
<tr key={channel.id}>
|
||||
<td>
|
||||
<p className='more-channel-name'>{channel.display_name}</p>
|
||||
<p className='more-channel-description'>{channel.description}</p>
|
||||
<p className='more-name'>{channel.display_name}</p>
|
||||
<p className='more-description'>{channel.description}</p>
|
||||
</td>
|
||||
<td className='td--action'>
|
||||
{joinButton}
|
||||
|
||||
@@ -140,12 +140,12 @@ export default class MoreDirectChannels extends React.Component {
|
||||
if (user.nickname) {
|
||||
const separator = fullName ? ' - ' : '';
|
||||
details.push(
|
||||
<span
|
||||
<p
|
||||
key={`${user.nickname}__nickname`}
|
||||
className='nickname'
|
||||
className='more-description'
|
||||
>
|
||||
{separator + user.nickname}
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,31 +170,38 @@ export default class MoreDirectChannels extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
key={user.id}
|
||||
className='direct-channel'
|
||||
>
|
||||
<div className='col-xs-1 image-div'>
|
||||
<tr>
|
||||
<td
|
||||
key={user.id}
|
||||
className='direct-channel'
|
||||
>
|
||||
<img
|
||||
className='profile-image'
|
||||
className='profile-img pull-left'
|
||||
width='38'
|
||||
height='38'
|
||||
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-xs-9'>
|
||||
<div className='username'>
|
||||
<div className='more-name'>
|
||||
{user.username}
|
||||
</div>
|
||||
<div>
|
||||
{details}
|
||||
</div>
|
||||
</div>
|
||||
<div className='col-xs-2 btn-div'>
|
||||
{details}
|
||||
</td>
|
||||
<td className='td--action lg'>
|
||||
{joinButton}
|
||||
</div>
|
||||
</li>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.show && this.props.show) {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300);
|
||||
if ($(window).width() > 768) {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
@@ -213,7 +220,7 @@ export default class MoreDirectChannels extends React.Component {
|
||||
const userEntries = users.map(this.createRowForUser);
|
||||
|
||||
if (userEntries.length === 0) {
|
||||
userEntries.push(<li key='no-users-found'>{'No users found :('}</li>);
|
||||
userEntries.push(<tr key='no-users-found'><td>{'No users found :('}</td></tr>);
|
||||
}
|
||||
|
||||
let memberString = 'Member';
|
||||
@@ -232,26 +239,35 @@ export default class MoreDirectChannels extends React.Component {
|
||||
<Modal
|
||||
className='modal-direct-channels'
|
||||
show={this.props.show}
|
||||
bsSize='large'
|
||||
onHide={this.handleHide}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'More Direct Messages'}</Modal.Title>
|
||||
<Modal.Title>{'Team Directory'}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div>
|
||||
<input
|
||||
ref='filter'
|
||||
className='form-control filter-textbox'
|
||||
placeholder='Search members'
|
||||
onInput={this.handleFilterChange}
|
||||
style={{width: '200px', display: 'inline'}}
|
||||
/>
|
||||
<span className='member-count pull-right'>{count}</span>
|
||||
<div className='row filter-row'>
|
||||
<div className='col-sm-6'>
|
||||
<input
|
||||
ref='filter'
|
||||
className='form-control filter-textbox'
|
||||
placeholder='Search members'
|
||||
onInput={this.handleFilterChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6'>
|
||||
<span className='member-count'>{count}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref='userList'
|
||||
className='user-list'
|
||||
>
|
||||
<table className='more-table table'>
|
||||
<tbody>
|
||||
{userEntries}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul className='user-list'>
|
||||
{userEntries}
|
||||
</ul>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var SocketStore = require('../stores/socket_store.jsx');
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
const SocketStore = require('../stores/socket_store.jsx');
|
||||
const UserStore = require('../stores/user_store.jsx');
|
||||
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
const SocketEvents = Constants.SocketEvents;
|
||||
|
||||
export default class MsgTyping extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -33,9 +36,9 @@ export default class MsgTyping extends React.Component {
|
||||
}
|
||||
|
||||
onChange(msg) {
|
||||
if (msg.action === 'typing' &&
|
||||
this.props.channelId === msg.channel_id &&
|
||||
this.props.parentId === msg.props.parent_id) {
|
||||
if (msg.action === SocketEvents.TYPING &&
|
||||
this.props.channelId === msg.channel_id &&
|
||||
this.props.parentId === msg.props.parent_id) {
|
||||
this.lastTime = new Date().getTime();
|
||||
|
||||
var username = 'Someone';
|
||||
@@ -52,7 +55,7 @@ export default class MsgTyping extends React.Component {
|
||||
}
|
||||
}.bind(this), 3000);
|
||||
}
|
||||
} else if (msg.action === 'posted' && msg.channel_id === this.props.channelId) {
|
||||
} else if (msg.action === SocketEvents.POSTED && msg.channel_id === this.props.channelId) {
|
||||
this.setState({text: ''});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,20 @@ export default class PopoverListMembers extends React.Component {
|
||||
const teamMembers = UserStore.getProfilesUsernameMap();
|
||||
|
||||
if (members && teamMembers) {
|
||||
members.sort(function compareByLocal(a, b) {
|
||||
members.sort((a, b) => {
|
||||
return a.username.localeCompare(b.username);
|
||||
});
|
||||
|
||||
members.forEach(function addMemberElement(m) {
|
||||
members.forEach((m, i) => {
|
||||
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
|
||||
popoverHtml.push(<div className='text--nowrap'>{m.username}</div>);
|
||||
popoverHtml.push(
|
||||
<div
|
||||
className='text--nowrap'
|
||||
key={'popover-member-' + i}
|
||||
>
|
||||
{m.username}
|
||||
</div>
|
||||
);
|
||||
count++;
|
||||
}
|
||||
});
|
||||
@@ -57,8 +64,15 @@ export default class PopoverListMembers extends React.Component {
|
||||
<OverlayTrigger
|
||||
trigger='click'
|
||||
placement='bottom'
|
||||
rootClose='true'
|
||||
overlay={<Popover title='Members'>{popoverHtml}</Popover>}
|
||||
rootClose={true}
|
||||
overlay={
|
||||
<Popover
|
||||
title='Members'
|
||||
id='member-list-popover'
|
||||
>
|
||||
{popoverHtml}
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<div id='member_popover'>
|
||||
<div>
|
||||
|
||||
@@ -116,7 +116,7 @@ export default class PostBody extends React.Component {
|
||||
}
|
||||
var metadata = data.items[0].snippet;
|
||||
this.receivedYoutubeData = true;
|
||||
this.setState({youtubeUploader: metadata.channelTitle, youtubeTitle: metadata.title});
|
||||
this.setState({youtubeTitle: metadata.title});
|
||||
}
|
||||
|
||||
if (global.window.mm_config.GoogleDeveloperKey && !this.receivedYoutubeData) {
|
||||
@@ -134,18 +134,12 @@ export default class PostBody extends React.Component {
|
||||
header = header + ' - ';
|
||||
}
|
||||
|
||||
let uploader = this.state.youtubeUploader;
|
||||
if (!uploader) {
|
||||
uploader = 'unknown';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='post-comment'>
|
||||
<h4>
|
||||
<span className='video-type'>{header}</span>
|
||||
<span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span>
|
||||
</h4>
|
||||
<h4 className='video-uploader'>{uploader}</h4>
|
||||
<div
|
||||
className='video-div embed-responsive-item'
|
||||
id={youtubeId}
|
||||
|
||||
@@ -150,7 +150,7 @@ export default class PostInfo extends React.Component {
|
||||
<ul className='post-header post-info'>
|
||||
<li className='post-header-col'>
|
||||
<OverlayTrigger
|
||||
delayShow='500'
|
||||
delayShow={500}
|
||||
container={this}
|
||||
placement='top'
|
||||
overlay={tooltip}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var PostStore = require('../stores/post_store.jsx');
|
||||
var ChannelStore = require('../stores/channel_store.jsx');
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
var PreferenceStore = require('../stores/preference_store.jsx');
|
||||
var UserProfile = require('./user_profile.jsx');
|
||||
var AsyncClient = require('../utils/async_client.jsx');
|
||||
var Post = require('./post.jsx');
|
||||
var LoadingScreen = require('./loading_screen.jsx');
|
||||
var SocketStore = require('../stores/socket_store.jsx');
|
||||
var utils = require('../utils/utils.jsx');
|
||||
var Client = require('../utils/client.jsx');
|
||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
var ActionTypes = Constants.ActionTypes;
|
||||
const Post = require('./post.jsx');
|
||||
const UserProfile = require('./user_profile.jsx');
|
||||
const AsyncClient = require('../utils/async_client.jsx');
|
||||
const LoadingScreen = require('./loading_screen.jsx');
|
||||
|
||||
const PostStore = require('../stores/post_store.jsx');
|
||||
const ChannelStore = require('../stores/channel_store.jsx');
|
||||
const UserStore = require('../stores/user_store.jsx');
|
||||
const SocketStore = require('../stores/socket_store.jsx');
|
||||
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||
|
||||
const utils = require('../utils/utils.jsx');
|
||||
const Client = require('../utils/client.jsx');
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
const SocketEvents = Constants.SocketEvents;
|
||||
|
||||
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||
|
||||
export default class PostList extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -58,7 +62,7 @@ export default class PostList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
postList.order.sort(function postSort(a, b) {
|
||||
postList.order.sort((a, b) => {
|
||||
if (postList.posts[a].create_at > postList.posts[b].create_at) {
|
||||
return -1;
|
||||
}
|
||||
@@ -82,7 +86,7 @@ export default class PostList extends React.Component {
|
||||
}
|
||||
|
||||
return {
|
||||
postList: postList
|
||||
postList
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
@@ -263,14 +267,14 @@ export default class PostList extends React.Component {
|
||||
Client.getPosts(
|
||||
id,
|
||||
PostStore.getLatestUpdate(id),
|
||||
function success() {
|
||||
() => {
|
||||
this.loadInProgress = false;
|
||||
this.setState({isFirstLoadComplete: true});
|
||||
}.bind(this),
|
||||
function fail() {
|
||||
},
|
||||
() => {
|
||||
this.loadInProgress = false;
|
||||
this.setState({isFirstLoadComplete: true});
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
}
|
||||
onChange() {
|
||||
@@ -281,28 +285,16 @@ export default class PostList extends React.Component {
|
||||
}
|
||||
}
|
||||
onSocketChange(msg) {
|
||||
var post;
|
||||
if (msg.action === 'posted' || msg.action === 'post_edited') {
|
||||
post = JSON.parse(msg.props.post);
|
||||
PostStore.storePost(post);
|
||||
} else if (msg.action === 'post_deleted') {
|
||||
if (msg.action === SocketEvents.POST_DELETED) {
|
||||
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
|
||||
var activeRootPostId = '';
|
||||
if (activeRoot && activeRoot.id.length > 0) {
|
||||
activeRootPostId = activeRoot.id;
|
||||
}
|
||||
|
||||
post = JSON.parse(msg.props.post);
|
||||
|
||||
PostStore.storeUnseenDeletedPost(post);
|
||||
PostStore.removePost(post, true);
|
||||
PostStore.emitChange();
|
||||
|
||||
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
|
||||
$('#post_deleted').modal('show');
|
||||
}
|
||||
} else if (msg.action === 'new_user') {
|
||||
AsyncClient.getProfiles();
|
||||
}
|
||||
}
|
||||
onTimeChange() {
|
||||
@@ -352,7 +344,7 @@ export default class PostList extends React.Component {
|
||||
data-title={channel.display_name}
|
||||
data-channelid={channel.id}
|
||||
>
|
||||
<i className='fa fa-pencil'></i>Set a description
|
||||
<i className='fa fa-pencil'></i>{'Set a description'}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class RhsComment extends React.Component {
|
||||
|
||||
var post = this.props.post;
|
||||
Client.createPost(post, post.channel_id,
|
||||
function success(data) {
|
||||
(data) => {
|
||||
AsyncClient.getPosts(post.channel_id);
|
||||
|
||||
var channel = ChannelStore.get(post.channel_id);
|
||||
@@ -43,11 +43,11 @@ export default class RhsComment extends React.Component {
|
||||
post: data
|
||||
});
|
||||
},
|
||||
function fail() {
|
||||
() => {
|
||||
post.state = Constants.POST_FAILED;
|
||||
PostStore.updatePendingPost(post);
|
||||
this.forceUpdate();
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
post.state = Constants.POST_LOADING;
|
||||
@@ -84,7 +84,10 @@ export default class RhsComment extends React.Component {
|
||||
|
||||
if (isOwner) {
|
||||
dropdownContents.push(
|
||||
<li role='presentation'>
|
||||
<li
|
||||
role='presentation'
|
||||
key='edit-button'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
role='menuitem'
|
||||
@@ -95,7 +98,7 @@ export default class RhsComment extends React.Component {
|
||||
data-postid={post.id}
|
||||
data-channelid={post.channel_id}
|
||||
>
|
||||
Edit
|
||||
{'Edit'}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
@@ -103,7 +106,10 @@ export default class RhsComment extends React.Component {
|
||||
|
||||
if (isOwner || isAdmin) {
|
||||
dropdownContents.push(
|
||||
<li role='presentation'>
|
||||
<li
|
||||
role='presentation'
|
||||
key='delete-button'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
role='menuitem'
|
||||
@@ -114,7 +120,7 @@ export default class RhsComment extends React.Component {
|
||||
data-channelid={post.channel_id}
|
||||
data-comments={0}
|
||||
>
|
||||
Delete
|
||||
{'Delete'}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
@@ -162,7 +168,7 @@ export default class RhsComment extends React.Component {
|
||||
href='#'
|
||||
onClick={this.retryComment}
|
||||
>
|
||||
Retry
|
||||
{'Retry'}
|
||||
</a>
|
||||
);
|
||||
} else if (post.state === Constants.POST_LOADING) {
|
||||
@@ -213,14 +219,14 @@ export default class RhsComment extends React.Component {
|
||||
</li>
|
||||
</ul>
|
||||
<div className='post-body'>
|
||||
<p className={postClass}>
|
||||
<div className={postClass}>
|
||||
{loading}
|
||||
<div
|
||||
ref='message_holder'
|
||||
onClick={TextFormatting.handleClick}
|
||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message)}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
{fileAttachment}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
const AsyncClient = require('../utils/async_client.jsx');
|
||||
const BrowserStore = require('../stores/browser_store.jsx');
|
||||
const ChannelStore = require('../stores/channel_store.jsx');
|
||||
const Client = require('../utils/client.jsx');
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
@@ -11,7 +10,6 @@ const NewChannelFlow = require('./new_channel_flow.jsx');
|
||||
const MoreDirectChannels = require('./more_direct_channels.jsx');
|
||||
const SearchBox = require('./search_bar.jsx');
|
||||
const SidebarHeader = require('./sidebar_header.jsx');
|
||||
const SocketStore = require('../stores/socket_store.jsx');
|
||||
const TeamStore = require('../stores/team_store.jsx');
|
||||
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
|
||||
const UserStore = require('../stores/user_store.jsx');
|
||||
@@ -46,7 +44,7 @@ export default class Sidebar extends React.Component {
|
||||
|
||||
const state = this.getStateFromStores();
|
||||
state.newChannelModalType = '';
|
||||
state.showMoreDirectChannelsModal = false;
|
||||
state.showDirectChannelsModal = false;
|
||||
state.loadingDMChannel = -1;
|
||||
|
||||
this.state = state;
|
||||
@@ -129,10 +127,11 @@ export default class Sidebar extends React.Component {
|
||||
UserStore.addChangeListener(this.onChange);
|
||||
UserStore.addStatusesChangeListener(this.onChange);
|
||||
TeamStore.addChangeListener(this.onChange);
|
||||
SocketStore.addChangeListener(this.onSocketChange);
|
||||
PreferenceStore.addChangeListener(this.onChange);
|
||||
|
||||
$('.nav-pills__container').perfectScrollbar();
|
||||
if ($(window).width() > 768) {
|
||||
$('.nav-pills__container').perfectScrollbar();
|
||||
}
|
||||
|
||||
this.updateTitle();
|
||||
this.updateUnreadIndicators();
|
||||
@@ -160,7 +159,6 @@ export default class Sidebar extends React.Component {
|
||||
UserStore.removeChangeListener(this.onChange);
|
||||
UserStore.removeStatusesChangeListener(this.onChange);
|
||||
TeamStore.removeChangeListener(this.onChange);
|
||||
SocketStore.removeChangeListener(this.onSocketChange);
|
||||
PreferenceStore.removeChangeListener(this.onChange);
|
||||
}
|
||||
onChange() {
|
||||
@@ -169,94 +167,6 @@ export default class Sidebar extends React.Component {
|
||||
this.setState(newState);
|
||||
}
|
||||
}
|
||||
onSocketChange(msg) {
|
||||
if (msg.action === 'posted') {
|
||||
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||
if (window.isActive) {
|
||||
AsyncClient.updateLastViewedAt();
|
||||
}
|
||||
} else {
|
||||
AsyncClient.getChannels();
|
||||
}
|
||||
|
||||
if (UserStore.getCurrentId() !== msg.user_id) {
|
||||
var mentions = [];
|
||||
if (msg.props.mentions) {
|
||||
mentions = JSON.parse(msg.props.mentions);
|
||||
}
|
||||
var channel = ChannelStore.get(msg.channel_id);
|
||||
|
||||
const user = UserStore.getCurrentUser();
|
||||
const member = ChannelStore.getMember(msg.channel_id);
|
||||
|
||||
var notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
|
||||
if (notifyLevel === 'default') {
|
||||
notifyLevel = user.notify_props.desktop;
|
||||
}
|
||||
|
||||
if (notifyLevel === 'none') {
|
||||
return;
|
||||
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
|
||||
return;
|
||||
}
|
||||
|
||||
var username = 'Someone';
|
||||
if (UserStore.hasProfile(msg.user_id)) {
|
||||
username = UserStore.getProfile(msg.user_id).username;
|
||||
}
|
||||
|
||||
var title = 'Posted';
|
||||
if (channel) {
|
||||
title = channel.display_name;
|
||||
}
|
||||
|
||||
var repRegex = new RegExp('<br>', 'g');
|
||||
var post = JSON.parse(msg.props.post);
|
||||
var msgProps = msg.props;
|
||||
var notifyText = post.message.replace(repRegex, '\n').replace(/\n+/g, ' ').replace('<mention>', '').replace('</mention>', '');
|
||||
|
||||
if (notifyText.length > 50) {
|
||||
notifyText = notifyText.substring(0, 49) + '...';
|
||||
}
|
||||
|
||||
if (notifyText.length === 0) {
|
||||
if (msgProps.image) {
|
||||
Utils.notifyMe(title, username + ' uploaded an image', channel);
|
||||
} else if (msgProps.otherFile) {
|
||||
Utils.notifyMe(title, username + ' uploaded a file', channel);
|
||||
} else {
|
||||
Utils.notifyMe(title, username + ' did something new', channel);
|
||||
}
|
||||
} else {
|
||||
Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
|
||||
}
|
||||
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
|
||||
Utils.ding();
|
||||
}
|
||||
}
|
||||
} else if (msg.action === 'viewed') {
|
||||
if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
|
||||
AsyncClient.getChannel(msg.channel_id);
|
||||
}
|
||||
} else if (msg.action === 'user_added') {
|
||||
if (UserStore.getCurrentId() === msg.user_id) {
|
||||
AsyncClient.getChannel(msg.channel_id);
|
||||
}
|
||||
} else if (msg.action === 'user_removed') {
|
||||
if (msg.user_id === UserStore.getCurrentId()) {
|
||||
AsyncClient.getChannels(true);
|
||||
|
||||
if (msg.props.remover !== msg.user_id && msg.props.channel_id === ChannelStore.getCurrentId() && $('#removed_from_channel').length > 0) {
|
||||
var sentState = {};
|
||||
sentState.channelName = ChannelStore.getCurrent().display_name;
|
||||
sentState.remover = UserStore.getProfile(msg.props.remover).username;
|
||||
|
||||
BrowserStore.setItem('channel-removed-state', sentState);
|
||||
$('#removed_from_channel').modal('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTitle() {
|
||||
const channel = ChannelStore.getCurrent();
|
||||
if (channel) {
|
||||
@@ -471,11 +381,13 @@ export default class Sidebar extends React.Component {
|
||||
}
|
||||
|
||||
let closeButton = null;
|
||||
const removeTooltip = <Tooltip>{'Remove from list'}</Tooltip>;
|
||||
const removeTooltip = (
|
||||
<Tooltip id='remove-dm-tooltip'>{'Remove from list'}</Tooltip>
|
||||
);
|
||||
if (handleClose && !badge) {
|
||||
closeButton = (
|
||||
<OverlayTrigger
|
||||
delayShow='1000'
|
||||
delayShow={1000}
|
||||
placement='top'
|
||||
overlay={removeTooltip}
|
||||
>
|
||||
@@ -564,8 +476,12 @@ export default class Sidebar extends React.Component {
|
||||
showChannelModal = true;
|
||||
}
|
||||
|
||||
const createChannelTootlip = <Tooltip>{'Create new channel'}</Tooltip>;
|
||||
const createGroupTootlip = <Tooltip>{'Create new group'}</Tooltip>;
|
||||
const createChannelTootlip = (
|
||||
<Tooltip id='new-channel-tooltip' >{'Create new channel'}</Tooltip>
|
||||
);
|
||||
const createGroupTootlip = (
|
||||
<Tooltip id='new-group-tooltip'>{'Create new group'}</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -607,7 +523,7 @@ export default class Sidebar extends React.Component {
|
||||
<h4>
|
||||
{'Channels'}
|
||||
<OverlayTrigger
|
||||
delayShow='500'
|
||||
delayShow={500}
|
||||
placement='top'
|
||||
overlay={createChannelTootlip}
|
||||
>
|
||||
@@ -640,7 +556,7 @@ export default class Sidebar extends React.Component {
|
||||
<h4>
|
||||
{'Private Groups'}
|
||||
<OverlayTrigger
|
||||
delayShow='500'
|
||||
delayShow={500}
|
||||
placement='top'
|
||||
overlay={createGroupTootlip}
|
||||
>
|
||||
|
||||
@@ -15,7 +15,12 @@ export default class TeamSignupUsernamePage extends React.Component {
|
||||
}
|
||||
submitBack(e) {
|
||||
e.preventDefault();
|
||||
this.props.state.wizard = 'send_invites';
|
||||
if (global.window.config.SendEmailNotifications === 'true') {
|
||||
this.props.state.wizard = 'send_invites';
|
||||
} else {
|
||||
this.props.state.wizard = 'team_url';
|
||||
}
|
||||
|
||||
this.props.updateParent(this.props.state);
|
||||
}
|
||||
submitNext(e) {
|
||||
|
||||
@@ -65,22 +65,33 @@ export default class UserProfile extends React.Component {
|
||||
|
||||
var dataContent = [];
|
||||
dataContent.push(
|
||||
<img className='user-popover__image'
|
||||
<img
|
||||
className='user-popover__image'
|
||||
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at}
|
||||
height='128'
|
||||
width='128'
|
||||
key='user-popover-image'
|
||||
/>
|
||||
);
|
||||
|
||||
if (!global.window.mm_config.ShowEmailAddress === 'true') {
|
||||
dataContent.push(<div className='text-nowrap'>{'Email not shared'}</div>);
|
||||
dataContent.push(
|
||||
<div
|
||||
className='text-nowrap'
|
||||
key='user-popover-no-email'
|
||||
>
|
||||
{'Email not shared'}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
dataContent.push(
|
||||
<div
|
||||
data-toggle='tooltip'
|
||||
title="' + this.state.profile.email + '"
|
||||
title={this.state.profile.email}
|
||||
key='user-popover-email'
|
||||
>
|
||||
<a
|
||||
href="mailto:' + this.state.profile.email + '"
|
||||
href={'mailto:' + this.state.profile.email}
|
||||
className='text-nowrap text-lowercase user-popover__email'
|
||||
>
|
||||
{this.state.profile.email}
|
||||
@@ -93,15 +104,22 @@ export default class UserProfile extends React.Component {
|
||||
<OverlayTrigger
|
||||
trigger='click'
|
||||
placement='right'
|
||||
rootClose='true'
|
||||
overlay={<Popover title={this.state.profile.username}>{dataContent}</Popover>}
|
||||
rootClose={true}
|
||||
overlay={
|
||||
<Popover
|
||||
title={this.state.profile.username}
|
||||
id='user-profile-popover'
|
||||
>
|
||||
{dataContent}
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className='user-popover'
|
||||
id={'profile_' + this.uniqueId}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
<div
|
||||
className='user-popover'
|
||||
id={'profile_' + this.uniqueId}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,9 +152,8 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
<input type='radio'
|
||||
checked={!displayCustom}
|
||||
onChange={this.updateType.bind(this, 'premade')}
|
||||
>
|
||||
{'Theme Colors'}
|
||||
</input>
|
||||
/>
|
||||
{'Theme Colors'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -164,9 +163,8 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
<input type='radio'
|
||||
checked={displayCustom}
|
||||
onChange={this.updateType.bind(this, 'custom')}
|
||||
>
|
||||
{'Custom Theme'}
|
||||
</input>
|
||||
/>
|
||||
{'Custom Theme'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import { savePreferences } from '../../utils/client.jsx';
|
||||
import {savePreferences} from '../../utils/client.jsx';
|
||||
import SettingItemMin from '../setting_item_min.jsx';
|
||||
import SettingItemMax from '../setting_item_max.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
@@ -38,7 +38,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
);
|
||||
}
|
||||
handleClockRadio(militaryTime) {
|
||||
this.setState({militaryTime: militaryTime});
|
||||
this.setState({militaryTime});
|
||||
}
|
||||
updateSection(section) {
|
||||
this.setState(getDisplayStateFromStores());
|
||||
@@ -57,7 +57,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
const serverError = this.state.serverError || null;
|
||||
let clockSection;
|
||||
if (this.props.activeSection === 'clock') {
|
||||
let clockFormat = [false, false];
|
||||
const clockFormat = [false, false];
|
||||
if (this.state.militaryTime === 'true') {
|
||||
clockFormat[1] = true;
|
||||
} else {
|
||||
@@ -77,9 +77,8 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
type='radio'
|
||||
checked={clockFormat[0]}
|
||||
onChange={this.handleClockRadio.bind(this, 'false')}
|
||||
>
|
||||
12-hour clock (example: 4:00 PM)
|
||||
</input>
|
||||
/>
|
||||
{'12-hour clock (example: 4:00 PM)'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -89,9 +88,8 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
type='radio'
|
||||
checked={clockFormat[1]}
|
||||
onChange={this.handleClockRadio.bind(this, 'true')}
|
||||
>
|
||||
24-hour clock (example: 16:00)
|
||||
</input>
|
||||
/>
|
||||
{'24-hour clock (example: 16:00)'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -99,7 +97,6 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
</div>
|
||||
];
|
||||
|
||||
|
||||
clockSection = (
|
||||
<SettingItemMax
|
||||
title='Clock Display'
|
||||
@@ -138,13 +135,13 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
className='close'
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
ref='title'
|
||||
>
|
||||
>
|
||||
<i className='modal-back'></i>
|
||||
{'Display Settings'}
|
||||
</h4>
|
||||
|
||||
@@ -228,9 +228,8 @@ export default class NotificationsTab extends React.Component {
|
||||
<input type='radio'
|
||||
checked={notifyActive[0]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'all')}
|
||||
>
|
||||
For all activity
|
||||
</input>
|
||||
/>
|
||||
{'For all activity'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -240,9 +239,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='radio'
|
||||
checked={notifyActive[1]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'mention')}
|
||||
>
|
||||
Only for mentions and direct messages
|
||||
</input>
|
||||
/>
|
||||
{'Only for mentions and direct messages'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -252,9 +250,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='radio'
|
||||
checked={notifyActive[2]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'none')}
|
||||
>
|
||||
Never
|
||||
</input>
|
||||
/>
|
||||
{'Never'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,9 +317,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='radio'
|
||||
checked={soundActive[0]}
|
||||
onChange={this.handleSoundRadio.bind(this, 'true')}
|
||||
>
|
||||
On
|
||||
</input>
|
||||
/>
|
||||
{'On'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -332,9 +328,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='radio'
|
||||
checked={soundActive[1]}
|
||||
onChange={this.handleSoundRadio.bind(this, 'false')}
|
||||
>
|
||||
Off
|
||||
</input>
|
||||
/>
|
||||
{'Off'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -402,9 +397,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='radio'
|
||||
checked={emailActive[0]}
|
||||
onChange={this.handleEmailRadio.bind(this, 'true')}
|
||||
>
|
||||
On
|
||||
</input>
|
||||
/>
|
||||
{'On'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -414,9 +408,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='radio'
|
||||
checked={emailActive[1]}
|
||||
onChange={this.handleEmailRadio.bind(this, 'false')}
|
||||
>
|
||||
Off
|
||||
</input>
|
||||
/>
|
||||
{'Off'}
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -482,9 +475,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='checkbox'
|
||||
checked={this.state.firstNameKey}
|
||||
onChange={handleUpdateFirstNameKey}
|
||||
>
|
||||
{'Your case sensitive first name "' + user.first_name + '"'}
|
||||
</input>
|
||||
/>
|
||||
{'Your case sensitive first name "' + user.first_name + '"'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -502,9 +494,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='checkbox'
|
||||
checked={this.state.usernameKey}
|
||||
onChange={handleUpdateUsernameKey}
|
||||
>
|
||||
{'Your non-case sensitive username "' + user.username + '"'}
|
||||
</input>
|
||||
/>
|
||||
{'Your non-case sensitive username "' + user.username + '"'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -521,9 +512,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='checkbox'
|
||||
checked={this.state.mentionKey}
|
||||
onChange={handleUpdateMentionKey}
|
||||
>
|
||||
{'Your username mentioned "@' + user.username + '"'}
|
||||
</input>
|
||||
/>
|
||||
{'Your username mentioned "@' + user.username + '"'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -540,9 +530,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='checkbox'
|
||||
checked={this.state.allKey}
|
||||
onChange={handleUpdateAllKey}
|
||||
>
|
||||
{'Team-wide mentions "@all"'}
|
||||
</input>
|
||||
/>
|
||||
{'Team-wide mentions "@all"'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -559,9 +548,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='checkbox'
|
||||
checked={this.state.channelKey}
|
||||
onChange={handleUpdateChannelKey}
|
||||
>
|
||||
{'Channel-wide mentions "@channel"'}
|
||||
</input>
|
||||
/>
|
||||
{'Channel-wide mentions "@channel"'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -576,9 +564,8 @@ export default class NotificationsTab extends React.Component {
|
||||
type='checkbox'
|
||||
checked={this.state.customKeysChecked}
|
||||
onChange={this.updateCustomMentionKeys}
|
||||
>
|
||||
{'Other non-case sensitive words, separated by commas:'}
|
||||
</input>
|
||||
/>
|
||||
{'Other non-case sensitive words, separated by commas:'}
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
|
||||
@@ -6,6 +6,7 @@ const Utils = require('../utils/utils.jsx');
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
const ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
const KeyCodes = Constants.KeyCodes;
|
||||
|
||||
export default class ViewImageModal extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -63,11 +64,11 @@ export default class ViewImageModal extends React.Component {
|
||||
this.loadImage(id);
|
||||
}
|
||||
handleKeyPress(e) {
|
||||
if (!e) {
|
||||
if (!e || !this.props.show) {
|
||||
return;
|
||||
} else if (e.keyCode === 39) {
|
||||
} else if (e.keyCode === KeyCodes.RIGHT) {
|
||||
this.handleNext();
|
||||
} else if (e.keyCode === 37) {
|
||||
} else if (e.keyCode === KeyCodes.LEFT) {
|
||||
this.handlePrev();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ var ErrorBar = require('../components/error_bar.jsx');
|
||||
var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx');
|
||||
var AdminController = require('../components/admin_console/admin_controller.jsx');
|
||||
|
||||
export function setupAdminConsolePage() {
|
||||
export function setupAdminConsolePage(props) {
|
||||
ReactDOM.render(
|
||||
<AdminController />,
|
||||
<AdminController
|
||||
tab={props.ActiveTab}
|
||||
teamId={props.TeamId}
|
||||
/>,
|
||||
document.getElementById('admin_controller')
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||
var UserStore = require('./user_store.jsx');
|
||||
var ErrorStore = require('./error_store.jsx');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||
const UserStore = require('./user_store.jsx');
|
||||
const PostStore = require('./post_store.jsx');
|
||||
const ChannelStore = require('./channel_store.jsx');
|
||||
const BrowserStore = require('./browser_store.jsx');
|
||||
const ErrorStore = require('./error_store.jsx');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
var ActionTypes = Constants.ActionTypes;
|
||||
const Utils = require('../utils/utils.jsx');
|
||||
const AsyncClient = require('../utils/async_client.jsx');
|
||||
|
||||
var CHANGE_EVENT = 'change';
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
const SocketEvents = Constants.SocketEvents;
|
||||
|
||||
const CHANGE_EVENT = 'change';
|
||||
|
||||
var conn;
|
||||
|
||||
@@ -94,6 +101,39 @@ class SocketStoreClass extends EventEmitter {
|
||||
removeChangeListener(callback) {
|
||||
this.removeListener(CHANGE_EVENT, callback);
|
||||
}
|
||||
handleMessage(msg) {
|
||||
switch (msg.action) {
|
||||
case SocketEvents.POSTED:
|
||||
handleNewPostEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.POST_EDITED:
|
||||
handlePostEditEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.POST_DELETED:
|
||||
handlePostDeleteEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.NEW_USER:
|
||||
handleNewUserEvent();
|
||||
break;
|
||||
|
||||
case SocketEvents.USER_ADDED:
|
||||
handleUserAddedEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.USER_REMOVED:
|
||||
handleUserRemovedEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.CHANNEL_VIEWED:
|
||||
handleChannelViewedEvent(msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
sendMessage(msg) {
|
||||
if (conn && conn.readyState === WebSocket.OPEN) {
|
||||
conn.send(JSON.stringify(msg));
|
||||
@@ -104,6 +144,138 @@ class SocketStoreClass extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
function handleNewPostEvent(msg) {
|
||||
// Store post
|
||||
const post = JSON.parse(msg.props.post);
|
||||
PostStore.storePost(post);
|
||||
|
||||
// Update channel state
|
||||
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||
if (window.isActive) {
|
||||
AsyncClient.updateLastViewedAt();
|
||||
}
|
||||
} else {
|
||||
AsyncClient.getChannel(msg.channel_id);
|
||||
}
|
||||
|
||||
// Send desktop notification
|
||||
if (UserStore.getCurrentId() !== msg.user_id) {
|
||||
const msgProps = msg.props;
|
||||
|
||||
let mentions = [];
|
||||
if (msgProps.mentions) {
|
||||
mentions = JSON.parse(msg.props.mentions);
|
||||
}
|
||||
|
||||
const channel = ChannelStore.get(msg.channel_id);
|
||||
const user = UserStore.getCurrentUser();
|
||||
const member = ChannelStore.getMember(msg.channel_id);
|
||||
|
||||
let notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
|
||||
if (notifyLevel === 'default') {
|
||||
notifyLevel = user.notify_props.desktop;
|
||||
}
|
||||
|
||||
if (notifyLevel === 'none') {
|
||||
return;
|
||||
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
|
||||
return;
|
||||
}
|
||||
|
||||
let username = 'Someone';
|
||||
if (UserStore.hasProfile(msg.user_id)) {
|
||||
username = UserStore.getProfile(msg.user_id).username;
|
||||
}
|
||||
|
||||
let title = 'Posted';
|
||||
if (channel) {
|
||||
title = channel.display_name;
|
||||
}
|
||||
|
||||
let notifyText = post.message.replace(/\n+/g, ' ');
|
||||
if (notifyText.length > 50) {
|
||||
notifyText = notifyText.substring(0, 49) + '...';
|
||||
}
|
||||
|
||||
if (notifyText.length === 0) {
|
||||
if (msgProps.image) {
|
||||
Utils.notifyMe(title, username + ' uploaded an image', channel);
|
||||
} else if (msgProps.otherFile) {
|
||||
Utils.notifyMe(title, username + ' uploaded a file', channel);
|
||||
} else {
|
||||
Utils.notifyMe(title, username + ' did something new', channel);
|
||||
}
|
||||
} else {
|
||||
Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
|
||||
}
|
||||
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
|
||||
Utils.ding();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handlePostEditEvent(msg) {
|
||||
// Store post
|
||||
const post = JSON.parse(msg.props.post);
|
||||
PostStore.storePost(post);
|
||||
|
||||
// Update channel state
|
||||
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||
if (window.isActive) {
|
||||
AsyncClient.updateLastViewedAt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handlePostDeleteEvent(msg) {
|
||||
const post = JSON.parse(msg.props.post);
|
||||
|
||||
PostStore.storeUnseenDeletedPost(post);
|
||||
PostStore.removePost(post, true);
|
||||
PostStore.emitChange();
|
||||
}
|
||||
|
||||
function handleNewUserEvent() {
|
||||
AsyncClient.getProfiles();
|
||||
AsyncClient.getChannelExtraInfo(true);
|
||||
}
|
||||
|
||||
function handleUserAddedEvent(msg) {
|
||||
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||
AsyncClient.getChannelExtraInfo(true);
|
||||
}
|
||||
|
||||
if (UserStore.getCurrentId() === msg.user_id) {
|
||||
AsyncClient.getChannel(msg.channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
function handleUserRemovedEvent(msg) {
|
||||
if (UserStore.getCurrentId() === msg.user_id) {
|
||||
AsyncClient.getChannels();
|
||||
|
||||
if (msg.props.remover_id !== msg.user_id &&
|
||||
msg.channel_id === ChannelStore.getCurrentId() &&
|
||||
$('#removed_from_channel').length > 0) {
|
||||
var sentState = {};
|
||||
sentState.channelName = ChannelStore.getCurrent().display_name;
|
||||
sentState.remover = UserStore.getProfile(msg.props.remover_id).username;
|
||||
|
||||
BrowserStore.setItem('channel-removed-state', sentState);
|
||||
$('#removed_from_channel').modal('show');
|
||||
}
|
||||
} else if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||
AsyncClient.getChannelExtraInfo(true);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChannelViewedEvent(msg) {
|
||||
// Useful for when multiple devices have the app open to different channels
|
||||
if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
|
||||
AsyncClient.getChannel(msg.channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
var SocketStore = new SocketStoreClass();
|
||||
|
||||
SocketStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
@@ -111,6 +283,7 @@ SocketStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
|
||||
switch (action.type) {
|
||||
case ActionTypes.RECIEVED_MSG:
|
||||
SocketStore.handleMessage(action.msg);
|
||||
SocketStore.emitChange(action.msg);
|
||||
break;
|
||||
|
||||
|
||||
@@ -46,6 +46,18 @@ module.exports = {
|
||||
SERVER_ACTION: null,
|
||||
VIEW_ACTION: null
|
||||
}),
|
||||
|
||||
SocketEvents: {
|
||||
POSTED: 'posted',
|
||||
POST_EDITED: 'post_edited',
|
||||
POST_DELETED: 'post_deleted',
|
||||
CHANNEL_VIEWED: 'channel_viewed',
|
||||
NEW_USER: 'new_user',
|
||||
USER_ADDED: 'user_added',
|
||||
USER_REMOVED: 'user_removed',
|
||||
TYPING: 'typing'
|
||||
},
|
||||
|
||||
SPECIAL_MENTIONS: ['all', 'channel'],
|
||||
CHARACTER_LIMIT: 4000,
|
||||
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
|
||||
|
||||
@@ -13,7 +13,8 @@ var client = require('./client.jsx');
|
||||
var Autolinker = require('autolinker');
|
||||
|
||||
export function isEmail(email) {
|
||||
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
|
||||
//var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
|
||||
var regex = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
|
||||
return regex.test(email);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
border: $border-gray;
|
||||
bottom: 38px;
|
||||
overflow: auto;
|
||||
z-index: 100;
|
||||
@extend %popover-box-shadow;
|
||||
.sidebar--right & {
|
||||
bottom: 100px;
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.more-channel-table {
|
||||
.more-table {
|
||||
margin: 0;
|
||||
table-layout: fixed;
|
||||
p {
|
||||
@@ -150,9 +150,11 @@
|
||||
@include opacity(0.8);
|
||||
margin: 5px 0;
|
||||
}
|
||||
.more-channel-name {
|
||||
.more-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
tbody {
|
||||
> tr {
|
||||
@@ -175,6 +177,9 @@
|
||||
padding: 8px 15px 8px 8px;
|
||||
width: 80px;
|
||||
vertical-align: middle;
|
||||
&.lg {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,47 +336,42 @@
|
||||
}
|
||||
|
||||
.modal-direct-channels {
|
||||
.user-list {
|
||||
list-style-type: none;
|
||||
margin: 15px 0px 0px;
|
||||
max-height: 600px;
|
||||
padding: 0px;
|
||||
overflow: auto;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #ddd;
|
||||
height: 60px;
|
||||
padding: 10px 0px;
|
||||
.user-list {
|
||||
margin-top: 20px;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-div {
|
||||
padding: 0px;
|
||||
.table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@include border-radius(20px);
|
||||
}
|
||||
}
|
||||
.modal-body {
|
||||
padding: 20px 0 0;
|
||||
@include clearfix;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: bold;
|
||||
}
|
||||
.filter-row {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
color: #888;
|
||||
}
|
||||
.member-count {
|
||||
margin-top: 5px;
|
||||
float: right;
|
||||
@include opacity(0.8);
|
||||
}
|
||||
|
||||
.btn-div {
|
||||
padding: 0px;
|
||||
.btn-message {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
.more-description {
|
||||
@include opacity(0.7);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.profile-img {
|
||||
-moz-border-radius: 50px;
|
||||
-webkit-border-radius: 50px;
|
||||
border-radius: 50px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +270,13 @@
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.modal-direct-channels {
|
||||
.member-count {
|
||||
float: none;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.center-file-overlay {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
@@ -230,13 +230,6 @@
|
||||
font-weight:500;
|
||||
}
|
||||
|
||||
.profile-img {
|
||||
width:128px;
|
||||
height:128px;
|
||||
margin-bottom: 10px;
|
||||
@include border-radius(128px);
|
||||
}
|
||||
|
||||
.sel-btn {
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,6 @@
|
||||
padding:0px;
|
||||
}
|
||||
|
||||
.video-uploader {
|
||||
font-size: 13px;
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size:15px;
|
||||
margin-top:3px;
|
||||
@@ -54,4 +49,4 @@
|
||||
border-top:36px solid transparent;
|
||||
border-bottom:36px solid transparent;
|
||||
border-left:60px solid rgba(255,255,255,0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div id='select_team_modal'></div>
|
||||
|
||||
<script>
|
||||
window.setup_admin_console_page();
|
||||
window.setup_admin_console_page({{ .Props }});
|
||||
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
14
web/web.go
14
web/web.go
@@ -77,6 +77,9 @@ func InitWeb() {
|
||||
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET")
|
||||
|
||||
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
|
||||
mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
|
||||
mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}", api.UserRequired(adminConsole)).Methods("GET")
|
||||
mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}/{team:[A-Za-z0-9-]*}", api.UserRequired(adminConsole)).Methods("GET")
|
||||
|
||||
mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
|
||||
|
||||
@@ -753,6 +756,7 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
|
||||
userChan := api.Srv.Store.User().Get(c.Session.UserId)
|
||||
|
||||
@@ -777,6 +781,16 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
page.User = user
|
||||
page.Team = team
|
||||
page.Session = &c.Session
|
||||
=======
|
||||
params := mux.Vars(r)
|
||||
activeTab := params["tab"]
|
||||
teamId := params["team"]
|
||||
|
||||
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
||||
|
||||
page.Props["ActiveTab"] = activeTab
|
||||
page.Props["TeamId"] = teamId
|
||||
>>>>>>> master
|
||||
page.Render(c, w)
|
||||
}
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user