Этот коммит содержится в:
Christopher Speller
2017-01-12 19:59:37 -05:00
родитель 4208e42ba9 aafb8be87c
Коммит 8fb66c2b83
61 изменённых файлов: 1801 добавлений и 1544 удалений

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

@@ -432,10 +432,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
);
}
if (global.window.mm_config.EnableSignInWithEmail !== 'true') {
passwordReset = null;
}
let makeDemoteModal = null;
if (this.props.user.id === me.id) {
const title = (

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

@@ -146,7 +146,11 @@ export default class PolicySettings extends AdminSettings {
defaultMessage='Set policy on who can delete public channels. Deleted channels can be recovered from the database using a {commandLineToolLink}.'
values={{
commandLineToolLink: (
<a href='https://docs.mattermost.com/administration/command-line-tools.html'>
<a
href='https://docs.mattermost.com/administration/command-line-tools.html'
target='_blank'
rel='noopener noreferrer'
>
<FormattedMessage
id='admin.general.policy.restrictPublicChannelDeletionCommandLineToolLink'
defaultMessage='command line tool'
@@ -222,7 +226,11 @@ export default class PolicySettings extends AdminSettings {
defaultMessage='Set policy on who can delete private groups. Deleted groups can be recovered from the database using a {commandLineToolLink}.'
values={{
commandLineToolLink: (
<a href='https://docs.mattermost.com/administration/command-line-tools.html'>
<a
href='https://docs.mattermost.com/administration/command-line-tools.html'
target='_blank'
rel='noopener noreferrer'
>
<FormattedMessage
id='admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink'
defaultMessage='command line tool'

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

@@ -296,9 +296,9 @@ export default class SamlSettings extends AdminSettings {
<SettingsGroup>
<div className='banner'>
<div className='banner__content'>
<FormattedMessage
<FormattedHTMLMessage
id='admin.saml.bannerDesc'
defaultMessage='If a user attribute changes on the SAML server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from a SAML Identity Provider. Remote logout with SAML servers is considered in a future release.'
defaultMessage='User attributes in SAML server, including user deactivation or removal, are updated in Mattermost during user login. Learn more at: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>'
/>
</div>
</div>

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

@@ -80,68 +80,140 @@ class SystemAnalytics extends React.Component {
render() {
const stats = this.state.stats;
const isLicensed = global.window.mm_license.IsLicensed === 'true';
const skippedIntensiveQueries = stats[StatTypes.TOTAL_POSTS] === -1;
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
let banner;
if (stats[StatTypes.TOTAL_POSTS] === -1) {
let postCount;
let postTotalGraph;
let activeUserGraph;
if (skippedIntensiveQueries) {
banner = (
<Banner
description={
<div className='banner'>
<div className='banner__content'>
<FormattedHTMLMessage
id='analytics.system.skippedIntensiveQueries'
defaultMessage="Some statistics have been omitted because they put too much load on the system to calculate. See <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a> for more details."
defaultMessage="To maximize performance, some statistics are disabled. You can re-enable them in config.json. See: <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>"
/>
</div>
</div>
);
} else {
postCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalPosts'
defaultMessage='Total Posts'
/>
}
icon='fa-comment'
count={stats[StatTypes.TOTAL_POSTS]}
/>
);
postTotalGraph = (
<div className='row'>
<LineChart
title={
<FormattedMessage
id='analytics.system.totalPosts'
defaultMessage='Total Posts'
/>
}
data={postCountsDay}
options={{
legend: {
display: false
}
}}
width='740'
height='225'
/>
</div>
);
activeUserGraph = (
<div className='row'>
<LineChart
title={
<FormattedMessage
id='analytics.system.activeUsers'
defaultMessage='Active Users With Posts'
/>
}
data={userCountsWithPostsDay}
options={{
legend: {
display: false
}
}}
width='740'
height='225'
/>
</div>
);
}
let advancedCounts;
let advancedStats;
let advancedGraphs;
let sessionCount;
let commandCount;
let incomingCount;
let outgoingCount;
if (global.window.mm_license.IsLicensed === 'true') {
advancedCounts = (
<div className='row'>
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalSessions'
defaultMessage='Total Sessions'
/>
}
icon='fa-signal'
count={stats[StatTypes.TOTAL_SESSIONS]}
/>
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalCommands'
defaultMessage='Total Commands'
/>
}
icon='fa-terminal'
count={stats[StatTypes.TOTAL_COMMANDS]}
/>
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalIncomingWebhooks'
defaultMessage='Incoming Webhooks'
/>
}
icon='fa-arrow-down'
count={stats[StatTypes.TOTAL_IHOOKS]}
/>
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalOutgoingWebhooks'
defaultMessage='Outgoing Webhooks'
/>
}
icon='fa-arrow-up'
count={stats[StatTypes.TOTAL_OHOOKS]}
/>
</div>
sessionCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalSessions'
defaultMessage='Total Sessions'
/>
}
icon='fa-signal'
count={stats[StatTypes.TOTAL_SESSIONS]}
/>
);
commandCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalCommands'
defaultMessage='Total Commands'
/>
}
icon='fa-terminal'
count={stats[StatTypes.TOTAL_COMMANDS]}
/>
);
incomingCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalIncomingWebhooks'
defaultMessage='Incoming Webhooks'
/>
}
icon='fa-arrow-down'
count={stats[StatTypes.TOTAL_IHOOKS]}
/>
);
outgoingCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalOutgoingWebhooks'
defaultMessage='Outgoing Webhooks'
/>
}
icon='fa-arrow-up'
count={stats[StatTypes.TOTAL_OHOOKS]}
/>
);
advancedStats = (
@@ -247,65 +319,89 @@ class SystemAnalytics extends React.Component {
}
}
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
let totalPostsCount;
let postTotalGraph;
let activeUserGraph;
if (stats[StatTypes.TOTAL_POSTS] !== -1) {
totalPostsCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalPosts'
defaultMessage='Total Posts'
/>
}
icon='fa-comment'
count={stats[StatTypes.TOTAL_POSTS]}
/>
);
postTotalGraph = (
<div className='row'>
<LineChart
title={
<FormattedMessage
id='analytics.system.totalPosts'
defaultMessage='Total Posts'
/>
}
data={postCountsDay}
options={{
legend: {
display: false
}
}}
width='740'
height='225'
const userCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalUsers'
defaultMessage='Total Users'
/>
}
icon='fa-user'
count={stats[StatTypes.TOTAL_USERS]}
/>
);
const teamCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalTeams'
defaultMessage='Total Teams'
/>
}
icon='fa-users'
count={stats[StatTypes.TOTAL_TEAMS]}
/>
);
const channelCount = (
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalChannels'
defaultMessage='Total Channels'
/>
}
icon='fa-globe'
count={stats[StatTypes.TOTAL_PUBLIC_CHANNELS] + stats[StatTypes.TOTAL_PRIVATE_GROUPS]}
/>
);
let firstRow;
let secondRow;
if (isLicensed && skippedIntensiveQueries) {
firstRow = (
<div className='row'>
{userCount}
{teamCount}
{channelCount}
{sessionCount}
</div>
);
activeUserGraph = (
secondRow = (
<div className='row'>
<LineChart
title={
<FormattedMessage
id='analytics.system.activeUsers'
defaultMessage='Active Users With Posts'
/>
}
data={userCountsWithPostsDay}
options={{
legend: {
display: false
}
}}
width='740'
height='225'
/>
{commandCount}
{incomingCount}
{outgoingCount}
</div>
);
} else if (isLicensed && !skippedIntensiveQueries) {
firstRow = (
<div className='row'>
{userCount}
{teamCount}
{channelCount}
{postCount}
</div>
);
secondRow = (
<div className='row'>
{sessionCount}
{commandCount}
{incomingCount}
{outgoingCount}
</div>
);
} else if (!isLicensed) {
firstRow = (
<div className='row'>
{userCount}
{teamCount}
{channelCount}
{postCount}
</div>
);
}
@@ -319,40 +415,8 @@ class SystemAnalytics extends React.Component {
/>
</h3>
{banner}
<div className='row'>
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalUsers'
defaultMessage='Total Users'
/>
}
icon='fa-user'
count={stats[StatTypes.TOTAL_USERS]}
/>
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalTeams'
defaultMessage='Total Teams'
/>
}
icon='fa-users'
count={stats[StatTypes.TOTAL_TEAMS]}
/>
{totalPostsCount}
<StatisticCount
title={
<FormattedMessage
id='analytics.system.totalChannels'
defaultMessage='Total Channels'
/>
}
icon='fa-globe'
count={stats[StatTypes.TOTAL_PUBLIC_CHANNELS] + stats[StatTypes.TOTAL_PRIVATE_GROUPS]}
/>
</div>
{advancedCounts}
{firstRow}
{secondRow}
{advancedStats}
{advancedGraphs}
{postTotalGraph}

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

@@ -168,7 +168,7 @@ export default class ChannelMembersModal extends React.Component {
return (
<div>
<Modal
dialogClassName='more-modal'
dialogClassName='more-modal more-modal--action'
show={this.state.show}
onHide={this.onHide}
onExited={this.props.onModalDismissed}
@@ -210,4 +210,4 @@ ChannelMembersModal.propTypes = {
onModalDismissed: React.PropTypes.func.isRequired,
showInviteModal: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired
};
};

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

@@ -67,9 +67,10 @@ export default class CreateComment extends React.Component {
fileInfos: draft.fileInfos,
submitting: false,
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
showPostDeletedModal: false,
lastBlurAt: 0
showPostDeletedModal: false
};
this.lastBlurAt = 0;
}
componentDidMount() {
@@ -145,7 +146,7 @@ export default class CreateComment extends React.Component {
});
const fasterThanHumanWillClick = 150;
const forceFocus = (Date.now() - this.state.lastBlurAt < fasterThanHumanWillClick);
const forceFocus = (Date.now() - this.lastBlurAt < fasterThanHumanWillClick);
this.focusTextbox(forceFocus);
}
@@ -405,7 +406,7 @@ export default class CreateComment extends React.Component {
}
handleBlur() {
this.setState({lastBlurAt: Date.now()});
this.lastBlurAt = Date.now();
}
render() {

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

@@ -76,9 +76,10 @@ export default class CreatePost extends React.Component {
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
fullWidthTextBox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
showTutorialTip: false,
showPostDeletedModal: false,
lastBlurAt: 0
showPostDeletedModal: false
};
this.lastBlurAt = 0;
}
handlePostError(postError) {
@@ -154,7 +155,8 @@ export default class CreatePost extends React.Component {
this.setState({message: '', submitting: false, postError: null, fileInfos: [], serverError: null});
const fasterThanHumanWillClick = 150;
const forceFocus = (Date.now() - this.state.lastBlurAt < fasterThanHumanWillClick);
const forceFocus = (Date.now() - this.lastBlurAt < fasterThanHumanWillClick);
this.focusTextbox(forceFocus);
}
@@ -438,7 +440,7 @@ export default class CreatePost extends React.Component {
}
handleBlur() {
this.setState({lastBlurAt: Date.now()});
this.lastBlurAt = Date.now();
}
showPostDeletedModal() {

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

@@ -67,7 +67,7 @@ export default class Setup extends React.Component {
}
let mfaRequired;
if (global.window.mm_config.EnforceMultifactorAuthentication) {
if (global.window.mm_config.EnforceMultifactorAuthentication === 'true') {
mfaRequired = (
<p>
<FormattedHTMLMessage

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

@@ -9,7 +9,6 @@ import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import {joinChannel, searchMoreChannels} from 'actions/channel_actions.jsx';
import React from 'react';
@@ -152,14 +151,19 @@ export default class MoreChannels extends React.Component {
const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
if (!ChannelUtils.showCreateOption(Constants.OPEN_CHANNEL, isAdmin, isSystemAdmin)) {
createNewChannelButton = null;
createChannelHelpText = null;
if (global.window.mm_license.IsLicensed === 'true') {
if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) {
createNewChannelButton = null;
createChannelHelpText = null;
} else if (global.window.mm_config.RestrictPublicChannelManagement === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) {
createNewChannelButton = null;
createChannelHelpText = null;
}
}
return (
<Modal
dialogClassName='more-modal more-public-channels'
dialogClassName='more-modal more-modal--action'
show={this.state.show}
onHide={this.handleHide}
onExited={this.handleExit}
@@ -192,4 +196,4 @@ export default class MoreChannels extends React.Component {
MoreChannels.propTypes = {
onModalDismissed: React.PropTypes.func,
handleNewChannel: React.PropTypes.func
};
};

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

@@ -195,7 +195,9 @@ export default class MoreDirectChannels extends React.Component {
render() {
let teamToggle;
let memberClass = '';
if (global.window.mm_config.RestrictDirectMessage === 'any') {
memberClass = 'more-system-members';
teamToggle = (
<div className='member-select__container'>
<select
@@ -226,7 +228,7 @@ export default class MoreDirectChannels extends React.Component {
return (
<Modal
dialogClassName='more-modal more-direct-channels'
dialogClassName={'more-modal more-direct-channels ' + memberClass}
show={this.state.show}
onHide={this.handleHide}
onExited={this.handleExit}
@@ -258,4 +260,4 @@ export default class MoreDirectChannels extends React.Component {
MoreDirectChannels.propTypes = {
onModalDismissed: React.PropTypes.func
};
};

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

@@ -13,7 +13,7 @@ function getCountsStateFromStores() {
teamMembers.forEach((member) => {
if (member.team_id !== TeamStore.getCurrentId()) {
count += ((member.msg_count || 0) + (member.mention_count || 0));
count += (member.mention_count || 0);
}
});
@@ -27,8 +27,6 @@ function getCountsStateFromStores() {
count += channel.total_msg_count - channelMember.msg_count;
} else if (channelMember.mention_count > 0) {
count += channelMember.mention_count;
} else if (channelMember.notify_props.mark_unread !== 'mention' && channel.total_msg_count - channelMember.msg_count > 0) {
count += 1;
}
});

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

@@ -136,7 +136,7 @@ export default class PostBodyAdditionalContent extends React.Component {
}
generateStaticEmbed() {
if (this.props.post.type === Constants.POST_TYPE_ATTACHMENT) {
if (this.props.post.props && this.props.post.props.attachments) {
return this.getSlackAttachment();
}

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

@@ -111,9 +111,8 @@ export default class SearchBar extends React.Component {
}
handleUserFocus() {
$('.search-bar__container').addClass('focused');
this.setState({focused: true});
$('.search-bar__container').addClass('focused');
}
performSearch(terms, isMentionSearch) {
@@ -193,19 +192,6 @@ export default class SearchBar extends React.Component {
</Tooltip>
);
const searchPopover = (
<Popover
id='searchbar-help-popup'
placement='bottom'
className={helpClass}
>
<FormattedHTMLMessage
id='search_bar.usage'
defaultMessage='<h4>Search Options</h4><ul><li><span>Use </span><b>"quotation marks"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>'
/>
</Popover>
);
const flaggedTooltip = (
<Tooltip id='flaggedTooltip'>
<FormattedMessage
@@ -289,28 +275,29 @@ export default class SearchBar extends React.Component {
autoComplete='off'
>
<span className='fa fa-search sidebar__search-icon'/>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY_SMALL}
<SuggestionBox
ref='search'
className='form-control search-bar'
placeholder={Utils.localizeMessage('search_bar.search', 'Search')}
value={this.state.searchTerm}
onFocus={this.handleUserFocus}
onBlur={this.handleUserBlur}
onChange={this.handleChange}
listComponent={SearchSuggestionList}
providers={this.suggestionProviders}
type='search'
/>
{isSearching}
<Popover
id='searchbar-help-popup'
placement='bottom'
overlay={searchPopover}
trigger='focus'
rootClose={true}
className={helpClass}
>
<SuggestionBox
ref='search'
className='form-control search-bar'
placeholder={Utils.localizeMessage('search_bar.search', 'Search')}
value={this.state.searchTerm}
onFocus={this.handleUserFocus}
onBlur={this.handleUserBlur}
onChange={this.handleChange}
listComponent={SearchSuggestionList}
providers={this.suggestionProviders}
type='search'
<FormattedHTMLMessage
id='search_bar.usage'
defaultMessage='<h4>Search Options</h4><ul><li><span>Use </span><b>"quotation marks"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>'
/>
</OverlayTrigger>
{isSearching}
</Popover>
</form>
{mentionBtn}

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

@@ -180,7 +180,9 @@ export default class SearchableChannelList extends React.Component {
ref='channelList'
className='more-modal__list'
>
{listContent}
<div>
{listContent}
</div>
</div>
<div className='filter-controls'>
{previousButton}
@@ -202,4 +204,4 @@ SearchableChannelList.propTypes = {
search: React.PropTypes.func.isRequired,
handleJoin: React.PropTypes.func.isRequired,
noResultsText: React.PropTypes.object
};
};

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

@@ -64,6 +64,10 @@ export default class SuggestionBox extends React.Component {
// Delay this slightly so that we don't clear the suggestions before we run click handlers on SuggestionList
GlobalActions.emitClearSuggestions(this.suggestionId);
}, 100);
if (this.props.onBlur) {
this.props.onBlur();
}
}
handleChange(e) {
@@ -272,6 +276,7 @@ SuggestionBox.propTypes = {
renderDividers: React.PropTypes.bool,
// explicitly name any input event handlers we override and need to manually call
onBlur: React.PropTypes.func,
onChange: React.PropTypes.func,
onKeyDown: React.PropTypes.func,
onItemSelected: React.PropTypes.func

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

@@ -7,9 +7,6 @@ import React from 'react';
import {Link} from 'react-router/es6';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import {isMobile} from 'utils/utils.jsx';
import {isMobileApp} from 'utils/user_agent.jsx';
export default class TeamButton extends React.Component {
constructor(props) {
super(props);
@@ -50,14 +47,21 @@ export default class TeamButton extends React.Component {
</div>
);
}
if (!isMobile() && !isMobileApp()) {
if (this.props.isMobile) {
btn = (
<div className={'team-btn ' + btnClass}>
{badge}
{content}
</div>
);
} else {
btn = (
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement={this.props.placement}
overlay={
<Tooltip id={`tooltip-${this.props.url}`}>
{this.props.displayName}
{this.props.tip}
</Tooltip>
}
>
@@ -67,13 +71,6 @@ export default class TeamButton extends React.Component {
</div>
</OverlayTrigger>
);
} else {
btn = (
<div className={'team-btn ' + btnClass}>
{badge}
{content}
</div>
);
}
return (
@@ -110,6 +107,7 @@ TeamButton.propTypes = {
tip: React.PropTypes.node.isRequired,
active: React.PropTypes.bool,
disabled: React.PropTypes.bool,
isMobile: React.PropTypes.bool,
unread: React.PropTypes.bool,
mentions: React.PropTypes.number,
placement: React.PropTypes.oneOf(['left', 'right', 'top', 'bottom'])

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

@@ -34,7 +34,8 @@ export default class TeamSidebar extends React.Component {
teamListings: TeamStore.getTeamListings(),
teamMembers,
currentTeamId,
show: teamMembers && teamMembers.length > 1
show: teamMembers && teamMembers.length > 1,
isMobile: Utils.isMobile()
};
}
@@ -53,12 +54,16 @@ export default class TeamSidebar extends React.Component {
}
componentDidUpdate(prevProps, prevState) {
$('.team-wrapper').perfectScrollbar();
if (!this.state.isMobile) {
$('.team-wrapper').perfectScrollbar();
}
// reset the scrollbar upon switching teams
if (this.state.currentTeam !== prevState.currentTeam) {
this.refs.container.scrollTop = 0;
$('.team-wrapper').perfectScrollbar('update');
if (!this.state.isMobile) {
$('.team-wrapper').perfectScrollbar('update');
}
}
}
@@ -121,6 +126,7 @@ export default class TeamSidebar extends React.Component {
url={`/${team.name}`}
tip={team.display_name}
active={team.id === this.state.currentTeamId}
isMobile={this.state.isMobile}
displayName={team.display_name}
unread={team.unread}
mentions={team.mentions}
@@ -134,6 +140,7 @@ export default class TeamSidebar extends React.Component {
btnClass='team-btn__add'
key='more_teams'
url='/select_team'
isMobile={this.state.isMobile}
tip={
<FormattedMessage
id='team_sidebar.join'
@@ -149,6 +156,7 @@ export default class TeamSidebar extends React.Component {
btnClass='team-btn__add'
key='more_teams'
url='/create_team'
isMobile={this.state.isMobile}
tip={
<FormattedMessage
id='navbar_dropdown.create'