Merge branch 'master' of https://github.com/mattermost/platform into plt-375

Conflicts:
	web/react/components/user_settings/user_settings_appearance.jsx
This commit is contained in:
Asaad Mahmood
2015-10-02 22:26:28 +05:00
31 changed files with 575 additions and 284 deletions

View File

@@ -73,6 +73,12 @@ export default class SqlSettings extends React.Component {
handleGenerate(e) {
e.preventDefault();
var cfm = global.window.confirm('Warning: re-generating this salt may cause some columns in the database to return empty results.');
if (cfm === false) {
return;
}
React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);

View File

@@ -15,14 +15,24 @@ export default class ChannelNotifications extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.updateSection = this.updateSection.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.handleRadioClick = this.handleRadioClick.bind(this);
this.handleQuietToggle = this.handleQuietToggle.bind(this);
this.createDesktopSection = this.createDesktopSection.bind(this);
this.createQuietSection = this.createQuietSection.bind(this);
this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''};
this.handleSubmitNotifyLevel = this.handleSubmitNotifyLevel.bind(this);
this.handleUpdateNotifyLevel = this.handleUpdateNotifyLevel.bind(this);
this.createNotifyLevelSection = this.createNotifyLevelSection.bind(this);
this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this);
this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
this.state = {
notifyLevel: '',
markUnreadLevel: '',
title: '',
channelId: '',
activeSection: ''
};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
@@ -30,33 +40,34 @@ export default class ChannelNotifications extends React.Component {
var button = e.relatedTarget;
var channelId = button.getAttribute('data-channelid');
var notifyLevel = ChannelStore.getMember(channelId).notify_level;
var quietMode = false;
const member = ChannelStore.getMember(channelId);
var notifyLevel = member.notify_props.desktop;
var markUnreadLevel = member.notify_props.mark_unread;
if (notifyLevel === 'quiet') {
quietMode = true;
}
this.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId});
this.setState({
notifyLevel,
markUnreadLevel,
title: button.getAttribute('data-title'),
channelId: channelId
});
}.bind(this));
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
}
onListenerChange() {
if (!this.state.channelId) {
return;
}
var notifyLevel = ChannelStore.getMember(this.state.channelId).notify_level;
var quietMode = false;
if (notifyLevel === 'quiet') {
quietMode = true;
}
const member = ChannelStore.getMember(this.state.channelId);
var notifyLevel = member.notify_props.desktop;
var markUnreadLevel = member.notify_props.mark_unread;
var newState = this.state;
newState.notifyLevel = notifyLevel;
newState.quietMode = quietMode;
newState.markUnreadLevel = markUnreadLevel;
if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
@@ -65,53 +76,64 @@ export default class ChannelNotifications extends React.Component {
updateSection(section) {
this.setState({activeSection: section});
}
handleUpdate() {
handleSubmitNotifyLevel() {
var channelId = this.state.channelId;
var notifyLevel = this.state.notifyLevel;
if (this.state.quietMode) {
notifyLevel = 'quiet';
if (ChannelStore.getMember(channelId).notify_props.desktop === notifyLevel) {
this.updateSection('');
return;
}
var data = {};
data.channel_id = channelId;
data.user_id = UserStore.getCurrentId();
data.notify_level = notifyLevel;
data.desktop = notifyLevel;
if (!data.notify_level || data.notify_level.length === 0) {
return;
}
Client.updateNotifyLevel(data,
function success() {
Client.updateNotifyProps(data,
() => {
var member = ChannelStore.getMember(channelId);
member.notify_level = notifyLevel;
member.notify_props.desktop = notifyLevel;
ChannelStore.setChannelMember(member);
this.updateSection('');
}.bind(this),
function error(err) {
},
(err) => {
this.setState({serverError: err.message});
}.bind(this)
}
);
}
handleRadioClick(notifyLevel) {
this.setState({notifyLevel: notifyLevel, quietMode: false});
handleUpdateNotifyLevel(notifyLevel) {
this.setState({notifyLevel});
React.findDOMNode(this.refs.modal).focus();
}
handleQuietToggle(quietMode) {
this.setState({notifyLevel: 'none', quietMode: quietMode});
React.findDOMNode(this.refs.modal).focus();
}
createDesktopSection(serverError) {
createNotifyLevelSection(serverError) {
var handleUpdateSection;
const user = UserStore.getCurrentUser();
const globalNotifyLevel = user.notify_props.desktop;
let globalNotifyLevelName;
if (globalNotifyLevel === 'all') {
globalNotifyLevelName = 'For all activity';
} else if (globalNotifyLevel === 'mention') {
globalNotifyLevelName = 'Only for mentions';
} else {
globalNotifyLevelName = 'Never';
}
if (this.state.activeSection === 'desktop') {
var notifyActive = [false, false, false];
if (this.state.notifyLevel === 'mention') {
notifyActive[1] = true;
} else if (this.state.notifyLevel === 'all') {
var notifyActive = [false, false, false, false];
if (this.state.notifyLevel === 'default') {
notifyActive[0] = true;
} else {
} else if (this.state.notifyLevel === 'all') {
notifyActive[1] = true;
} else if (this.state.notifyLevel === 'mention') {
notifyActive[2] = true;
} else {
notifyActive[3] = true;
}
var inputs = [];
@@ -123,9 +145,9 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={notifyActive[0]}
onChange={this.handleRadioClick.bind(this, 'all')}
onChange={this.handleUpdateNotifyLevel.bind(this, 'default')}
>
For all activity
{`Global default (${globalNotifyLevelName})`}
</input>
</label>
<br/>
@@ -135,9 +157,9 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={notifyActive[1]}
onChange={this.handleRadioClick.bind(this, 'mention')}
onChange={this.handleUpdateNotifyLevel.bind(this, 'all')}
>
Only for mentions
{'For all activity'}
</input>
</label>
<br/>
@@ -147,9 +169,21 @@ export default class ChannelNotifications extends React.Component {
<input
type='radio'
checked={notifyActive[2]}
onChange={this.handleRadioClick.bind(this, 'none')}
onChange={this.handleUpdateNotifyLevel.bind(this, 'mention')}
>
Never
{'Only for mentions'}
</input>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
type='radio'
checked={notifyActive[3]}
onChange={this.handleUpdateNotifyLevel.bind(this, 'none')}
>
{'Never'}
</input>
</label>
</div>
@@ -162,30 +196,19 @@ export default class ChannelNotifications extends React.Component {
e.preventDefault();
}.bind(this);
let curChannel = ChannelStore.get(this.state.channelId);
let extraInfo = (
const extraInfo = (
<span>
These settings will override the global notification settings.
{'Selecting an option other than "Default" will override the global notification settings.'}
<br/>
Desktop notifications are available on Firefox, Safari, and Chrome.
{'Desktop notifications are available on Firefox, Safari, and Chrome.'}
</span>
);
if (curChannel && curChannel.display_name) {
extraInfo = (
<span>
These settings will override the global notification settings for the <b>{curChannel.display_name}</b> channel.
<br/>
Desktop notifications are available on Firefox, Safari, and Chrome.
</span>
);
}
return (
<SettingItemMax
title='Send desktop notifications'
inputs={inputs}
submit={this.handleUpdate}
submit={this.handleSubmitNotifyLevel}
server_error={serverError}
updateSection={handleUpdateSection}
extraInfo={extraInfo}
@@ -194,7 +217,9 @@ export default class ChannelNotifications extends React.Component {
}
var describe;
if (this.state.notifyLevel === 'mention') {
if (this.state.notifyLevel === 'default') {
describe = `Global default (${globalNotifyLevelName})`;
} else if (this.state.notifyLevel === 'mention') {
describe = 'Only for mentions';
} else if (this.state.notifyLevel === 'all') {
describe = 'For all activity';
@@ -215,101 +240,123 @@ export default class ChannelNotifications extends React.Component {
/>
);
}
createQuietSection(serverError) {
var handleUpdateSection;
if (this.state.activeSection === 'quiet') {
var quietActive = [false, false];
if (this.state.quietMode) {
quietActive[0] = true;
} else {
quietActive[1] = true;
handleSubmitMarkUnreadLevel() {
const channelId = this.state.channelId;
const markUnreadLevel = this.state.markUnreadLevel;
if (ChannelStore.getMember(channelId).notify_props.mark_unread === markUnreadLevel) {
this.updateSection('');
return;
}
const data = {
channel_id: channelId,
user_id: UserStore.getCurrentId(),
mark_unread: markUnreadLevel
};
Client.updateNotifyProps(data,
() => {
var member = ChannelStore.getMember(channelId);
member.notify_props.mark_unread = markUnreadLevel;
ChannelStore.setChannelMember(member);
this.updateSection('');
},
(err) => {
this.setState({serverError: err.message});
}
);
}
var inputs = [];
handleUpdateMarkUnreadLevel(markUnreadLevel) {
this.setState({markUnreadLevel});
React.findDOMNode(this.refs.modal).focus();
}
inputs.push(
createMarkUnreadLevelSection(serverError) {
let content;
if (this.state.activeSection === 'markUnreadLevel') {
const inputs = [(
<div>
<div className='radio'>
<label>
<input
type='radio'
checked={quietActive[0]}
onChange={this.handleQuietToggle.bind(this, true)}
checked={this.state.markUnreadLevel === 'all'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'all')}
>
On
{'For all unread messages'}
</input>
</label>
<br/>
<br />
</div>
<div className='radio'>
<label>
<input
type='radio'
checked={quietActive[1]}
onChange={this.handleQuietToggle.bind(this, false)}
checked={this.state.markUnreadLevel === 'mention'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'mention')}
>
Off
{'Only for mentions'}
</input>
</label>
<br/>
<br />
</div>
</div>
);
)];
inputs.push(
<div>
<br/>
Enabling quiet mode will turn off desktop notifications and only mark the channel as unread if you have been mentioned.
</div>
);
handleUpdateSection = function updateSection(e) {
const handleUpdateSection = function handleUpdateSection(e) {
this.updateSection('');
this.onListenerChange();
e.preventDefault();
}.bind(this);
return (
const extraInfo = <span>{'The channel name is bolded in the sidebar when there are unread messages. Selecting "Only for mentions" will bold the channel only when you are mentioned.'}</span>;
content = (
<SettingItemMax
title='Quiet mode'
title='Mark Channel Unread'
inputs={inputs}
submit={this.handleUpdate}
submit={this.handleSubmitMarkUnreadLevel}
server_error={serverError}
updateSection={handleUpdateSection}
extraInfo={extraInfo}
/>
);
} else {
let describe;
if (!this.state.markUnreadLevel || this.state.markUnreadLevel === 'all') {
describe = 'For all unread messages';
} else {
describe = 'Only for mentions';
}
const handleUpdateSection = function handleUpdateSection(e) {
this.updateSection('markUnreadLevel');
e.preventDefault();
}.bind(this);
content = (
<SettingItemMin
title='Mark Channel Unread'
describe={describe}
updateSection={handleUpdateSection}
/>
);
}
var describe;
if (this.state.quietMode) {
describe = 'On';
} else {
describe = 'Off';
}
handleUpdateSection = function updateSection(e) {
this.updateSection('quiet');
e.preventDefault();
}.bind(this);
return (
<SettingItemMin
title='Quiet mode'
describe={describe}
updateSection={handleUpdateSection}
/>
);
return content;
}
render() {
var serverError = null;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var desktopSection = this.createDesktopSection(serverError);
var quietSection = this.createQuietSection(serverError);
return (
<div
className='modal fade'
@@ -341,9 +388,9 @@ export default class ChannelNotifications extends React.Component {
>
<br/>
<div className='divider-dark first'/>
{desktopSection}
{this.createNotifyLevelSection(serverError)}
<div className='divider-light'/>
{quietSection}
{this.createMarkUnreadLevelSection(serverError)}
<div className='divider-dark'/>
</div>
</div>

View File

@@ -15,7 +15,7 @@ function getCountsStateFromStores() {
count += channel.total_msg_count - channelMember.msg_count;
} else if (channelMember.mention_count > 0) {
count += channelMember.mention_count;
} else if (channelMember.notify_level !== 'quiet' && channel.total_msg_count - channelMember.msg_count > 0) {
} else if (channelMember.notify_props.mark_unread !== 'mention' && channel.total_msg_count - channelMember.msg_count > 0) {
count += 1;
}
});

View File

@@ -150,7 +150,7 @@ export default class PostInfo extends React.Component {
var dropdown = this.createDropdown();
let tooltip = <Tooltip id={post.id + 'tooltip'}>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>;
let tooltip = <Tooltip id={post.id + 'tooltip'}>{`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}</Tooltip>;
return (
<ul className='post-header post-info'>

View File

@@ -200,13 +200,17 @@ export default class Sidebar extends React.Component {
}
var channel = ChannelStore.get(msg.channel_id);
var user = UserStore.getCurrentUser();
if (user.notify_props && ((user.notify_props.desktop === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') || user.notify_props.desktop === 'none')) {
return;
const user = UserStore.getCurrentUser();
const member = ChannelStore.getMember(msg.channel_id);
var notifyLevel = member.notify_props.desktop;
if (notifyLevel === 'default') {
notifyLevel = user.notify_props.desktop;
}
var member = ChannelStore.getMember(msg.channel_id);
if ((member.notify_level === 'mention' && mentions.indexOf(user.id) === -1) || member.notify_level === 'none' || member.notify_level === 'quiet') {
if (notifyLevel === 'none') {
return;
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
return;
}
@@ -330,7 +334,7 @@ export default class Sidebar extends React.Component {
var unread = false;
if (channelMember) {
msgCount = channel.total_msg_count - channelMember.msg_count;
unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
unread = (msgCount > 0 && channelMember.notify_props.mark_unread !== 'mention') || channelMember.mention_count > 0;
}
var titleClass = '';

View File

@@ -148,6 +148,8 @@ export default class ManageIncomingHooks extends React.Component {
return (
<div key='addIncomingHook'>
{'Create webhook URLs for channels and private groups. These URLs can be used by outside applications to create posts in any channels or private groups you have access to. The specified channel will be used as the default.'}
<br/>
<label className='control-label'>{'Add a new incoming webhook'}</label>
<div className='padding-top'>
<select

View File

@@ -214,15 +214,14 @@ export default class UserSettingsAppearance extends React.Component {
<div className='divider-dark first'/>
{themeUI}
<div className='divider-dark'/>
<br/>
<a
href='#'
className='theme'
onClick={this.handleImportModal}
>
{'Import from Slack'}
</a>
</div>
<br/>
<a
className='theme'
onClick={this.handleImportModal}
>
{'Import theme colors from Slack'}
</a>
</div>
);
}

View File

@@ -17,7 +17,7 @@ function getNotificationsStateFromStores() {
if (user.notify_props && user.notify_props.desktop_sound) {
sound = user.notify_props.desktop_sound;
}
var desktop = 'all';
var desktop = 'default';
if (user.notify_props && user.notify_props.desktop) {
desktop = user.notify_props.desktop;
}

View File

@@ -152,21 +152,23 @@ export function getChannel(id) {
}
export function updateLastViewedAt() {
if (isCallInProgress('updateLastViewed')) {
const channelId = ChannelStore.getCurrentId();
if (channelId === null) {
return;
}
if (ChannelStore.getCurrentId() == null) {
if (isCallInProgress(`updateLastViewed${channelId}`)) {
return;
}
callTracker.updateLastViewed = utils.getTimestamp();
callTracker[`updateLastViewed${channelId}`] = utils.getTimestamp();
client.updateLastViewedAt(
ChannelStore.getCurrentId(),
function updateLastViewedAtSuccess() {
channelId,
() => {
callTracker.updateLastViewed = 0;
},
function updateLastViewdAtFailure(err) {
(err) => {
callTracker.updateLastViewed = 0;
dispatchError(err, 'updateLastViewedAt');
}
@@ -634,4 +636,4 @@ export function getMyTeam() {
dispatchError(err, 'getMyTeam');
}
);
}
}

View File

@@ -332,6 +332,20 @@ export function saveConfig(config, success, error) {
});
}
export function logClientError(msg) {
var l = {};
l.level = 'ERROR';
l.message = msg;
$.ajax({
url: '/api/v1/admin/log_client',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(l)
});
}
export function testEmail(config, success, error) {
$.ajax({
url: '/api/v1/admin/test_email',
@@ -568,16 +582,16 @@ export function updateChannelDesc(data, success, error) {
track('api', 'api_channels_desc');
}
export function updateNotifyLevel(data, success, error) {
export function updateNotifyProps(data, success, error) {
$.ajax({
url: '/api/v1/channels/update_notify_level',
url: '/api/v1/channels/update_notify_props',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success,
error: function onError(xhr, status, err) {
var e = handleError('updateNotifyLevel', xhr, status, err);
var e = handleError('updateNotifyProps', xhr, status, err);
error(e);
}
});

View File

@@ -87,8 +87,10 @@ function autolinkUrls(text, tokens) {
const linkText = match.getMatchedText();
let url = linkText;
if (url.lastIndexOf('http', 0) !== 0) {
url = `http://${linkText}`;
if (match.getType() === 'email') {
url = `mailto:${url}`;
} else if (url.lastIndexOf('http', 0) !== 0) {
url = `http://${url}`;
}
const index = tokens.size;