Fixing merge
Этот коммит содержится в:
@@ -10,7 +10,7 @@ var LoadingScreen = require('../loading_screen.jsx');
|
||||
var EmailSettingsTab = require('./email_settings.jsx');
|
||||
var LogSettingsTab = require('./log_settings.jsx');
|
||||
var LogsTab = require('./logs.jsx');
|
||||
var ImageSettingsTab = require('./image_settings.jsx');
|
||||
var FileSettingsTab = require('./image_settings.jsx');
|
||||
var PrivacySettingsTab = require('./privacy_settings.jsx');
|
||||
var RateSettingsTab = require('./rate_settings.jsx');
|
||||
var GitLabSettingsTab = require('./gitlab_settings.jsx');
|
||||
@@ -128,7 +128,7 @@ export default class AdminController extends React.Component {
|
||||
} else if (this.state.selected === 'logs') {
|
||||
tab = <LogsTab />;
|
||||
} else if (this.state.selected === 'image_settings') {
|
||||
tab = <ImageSettingsTab config={this.state.config} />;
|
||||
tab = <FileSettingsTab config={this.state.config} />;
|
||||
} else if (this.state.selected === 'privacy_settings') {
|
||||
tab = <PrivacySettingsTab config={this.state.config} />;
|
||||
} else if (this.state.selected === 'rate_settings') {
|
||||
|
||||
@@ -180,7 +180,7 @@ export default class AdminSidebar extends React.Component {
|
||||
className={this.isSelected('image_settings')}
|
||||
onClick={this.handleClick.bind(this, 'image_settings', null)}
|
||||
>
|
||||
{'Image Settings'}
|
||||
{'File Settings'}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -5,7 +5,7 @@ var Client = require('../../utils/client.jsx');
|
||||
var AsyncClient = require('../../utils/async_client.jsx');
|
||||
var crypto = require('crypto');
|
||||
|
||||
export default class ImageSettings extends React.Component {
|
||||
export default class FileSettings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class ImageSettings extends React.Component {
|
||||
this.state = {
|
||||
saveNeeded: false,
|
||||
serverError: null,
|
||||
DriverName: this.props.config.ImageSettings.DriverName
|
||||
DriverName: this.props.config.FileSettings.DriverName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,61 +42,61 @@ export default class ImageSettings extends React.Component {
|
||||
$('#save-button').button('loading');
|
||||
|
||||
var config = this.props.config;
|
||||
config.ImageSettings.DriverName = React.findDOMNode(this.refs.DriverName).value;
|
||||
config.ImageSettings.Directory = React.findDOMNode(this.refs.Directory).value;
|
||||
config.ImageSettings.AmazonS3AccessKeyId = React.findDOMNode(this.refs.AmazonS3AccessKeyId).value;
|
||||
config.ImageSettings.AmazonS3SecretAccessKey = React.findDOMNode(this.refs.AmazonS3SecretAccessKey).value;
|
||||
config.ImageSettings.AmazonS3Bucket = React.findDOMNode(this.refs.AmazonS3Bucket).value;
|
||||
config.ImageSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value;
|
||||
config.ImageSettings.EnablePublicLink = React.findDOMNode(this.refs.EnablePublicLink).checked;
|
||||
config.FileSettings.DriverName = React.findDOMNode(this.refs.DriverName).value;
|
||||
config.FileSettings.Directory = React.findDOMNode(this.refs.Directory).value;
|
||||
config.FileSettings.AmazonS3AccessKeyId = React.findDOMNode(this.refs.AmazonS3AccessKeyId).value;
|
||||
config.FileSettings.AmazonS3SecretAccessKey = React.findDOMNode(this.refs.AmazonS3SecretAccessKey).value;
|
||||
config.FileSettings.AmazonS3Bucket = React.findDOMNode(this.refs.AmazonS3Bucket).value;
|
||||
config.FileSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value;
|
||||
config.FileSettings.EnablePublicLink = React.findDOMNode(this.refs.EnablePublicLink).checked;
|
||||
|
||||
config.ImageSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim();
|
||||
config.FileSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim();
|
||||
|
||||
if (config.ImageSettings.PublicLinkSalt === '') {
|
||||
config.ImageSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
|
||||
React.findDOMNode(this.refs.PublicLinkSalt).value = config.ImageSettings.PublicLinkSalt;
|
||||
if (config.FileSettings.PublicLinkSalt === '') {
|
||||
config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
|
||||
React.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt;
|
||||
}
|
||||
|
||||
var thumbnailWidth = 120;
|
||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10))) {
|
||||
thumbnailWidth = parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10);
|
||||
}
|
||||
config.ImageSettings.ThumbnailWidth = thumbnailWidth;
|
||||
config.FileSettings.ThumbnailWidth = thumbnailWidth;
|
||||
React.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth;
|
||||
|
||||
var thumbnailHeight = 100;
|
||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10))) {
|
||||
thumbnailHeight = parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10);
|
||||
}
|
||||
config.ImageSettings.ThumbnailHeight = thumbnailHeight;
|
||||
config.FileSettings.ThumbnailHeight = thumbnailHeight;
|
||||
React.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight;
|
||||
|
||||
var previewWidth = 1024;
|
||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10))) {
|
||||
previewWidth = parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10);
|
||||
}
|
||||
config.ImageSettings.PreviewWidth = previewWidth;
|
||||
config.FileSettings.PreviewWidth = previewWidth;
|
||||
React.findDOMNode(this.refs.PreviewWidth).value = previewWidth;
|
||||
|
||||
var previewHeight = 0;
|
||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10))) {
|
||||
previewHeight = parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10);
|
||||
}
|
||||
config.ImageSettings.PreviewHeight = previewHeight;
|
||||
config.FileSettings.PreviewHeight = previewHeight;
|
||||
React.findDOMNode(this.refs.PreviewHeight).value = previewHeight;
|
||||
|
||||
var profileWidth = 128;
|
||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10))) {
|
||||
profileWidth = parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10);
|
||||
}
|
||||
config.ImageSettings.ProfileWidth = profileWidth;
|
||||
config.FileSettings.ProfileWidth = profileWidth;
|
||||
React.findDOMNode(this.refs.ProfileWidth).value = profileWidth;
|
||||
|
||||
var profileHeight = 128;
|
||||
if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10))) {
|
||||
profileHeight = parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10);
|
||||
}
|
||||
config.ImageSettings.ProfileHeight = profileHeight;
|
||||
config.FileSettings.ProfileHeight = profileHeight;
|
||||
React.findDOMNode(this.refs.ProfileHeight).value = profileHeight;
|
||||
|
||||
Client.saveConfig(
|
||||
@@ -143,7 +143,7 @@ export default class ImageSettings extends React.Component {
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed'>
|
||||
<h3>{'Image Settings'}</h3>
|
||||
<h3>{'File Settings'}</h3>
|
||||
<form
|
||||
className='form-horizontal'
|
||||
role='form'
|
||||
@@ -161,7 +161,7 @@ export default class ImageSettings extends React.Component {
|
||||
className='form-control'
|
||||
id='DriverName'
|
||||
ref='DriverName'
|
||||
defaultValue={this.props.config.ImageSettings.DriverName}
|
||||
defaultValue={this.props.config.FileSettings.DriverName}
|
||||
onChange={this.handleChange.bind(this, 'DriverName')}
|
||||
>
|
||||
<option value=''>{'Disable File Storage'}</option>
|
||||
@@ -185,7 +185,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='Directory'
|
||||
ref='Directory'
|
||||
placeholder='Ex "./data/"'
|
||||
defaultValue={this.props.config.ImageSettings.Directory}
|
||||
defaultValue={this.props.config.FileSettings.Directory}
|
||||
onChange={this.handleChange}
|
||||
disabled={!enableFile}
|
||||
/>
|
||||
@@ -207,7 +207,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='AmazonS3AccessKeyId'
|
||||
ref='AmazonS3AccessKeyId'
|
||||
placeholder='Ex "AKIADTOVBGERKLCBV"'
|
||||
defaultValue={this.props.config.ImageSettings.AmazonS3AccessKeyId}
|
||||
defaultValue={this.props.config.FileSettings.AmazonS3AccessKeyId}
|
||||
onChange={this.handleChange}
|
||||
disabled={!enableS3}
|
||||
/>
|
||||
@@ -229,7 +229,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='AmazonS3SecretAccessKey'
|
||||
ref='AmazonS3SecretAccessKey'
|
||||
placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
|
||||
defaultValue={this.props.config.ImageSettings.AmazonS3SecretAccessKey}
|
||||
defaultValue={this.props.config.FileSettings.AmazonS3SecretAccessKey}
|
||||
onChange={this.handleChange}
|
||||
disabled={!enableS3}
|
||||
/>
|
||||
@@ -251,7 +251,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='AmazonS3Bucket'
|
||||
ref='AmazonS3Bucket'
|
||||
placeholder='Ex "mattermost-media"'
|
||||
defaultValue={this.props.config.ImageSettings.AmazonS3Bucket}
|
||||
defaultValue={this.props.config.FileSettings.AmazonS3Bucket}
|
||||
onChange={this.handleChange}
|
||||
disabled={!enableS3}
|
||||
/>
|
||||
@@ -273,7 +273,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='AmazonS3Region'
|
||||
ref='AmazonS3Region'
|
||||
placeholder='Ex "us-east-1"'
|
||||
defaultValue={this.props.config.ImageSettings.AmazonS3Region}
|
||||
defaultValue={this.props.config.FileSettings.AmazonS3Region}
|
||||
onChange={this.handleChange}
|
||||
disabled={!enableS3}
|
||||
/>
|
||||
@@ -295,7 +295,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='ThumbnailWidth'
|
||||
ref='ThumbnailWidth'
|
||||
placeholder='Ex "120"'
|
||||
defaultValue={this.props.config.ImageSettings.ThumbnailWidth}
|
||||
defaultValue={this.props.config.FileSettings.ThumbnailWidth}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}</p>
|
||||
@@ -316,7 +316,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='ThumbnailHeight'
|
||||
ref='ThumbnailHeight'
|
||||
placeholder='Ex "100"'
|
||||
defaultValue={this.props.config.ImageSettings.ThumbnailHeight}
|
||||
defaultValue={this.props.config.FileSettings.ThumbnailHeight}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}</p>
|
||||
@@ -337,7 +337,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='PreviewWidth'
|
||||
ref='PreviewWidth'
|
||||
placeholder='Ex "1024"'
|
||||
defaultValue={this.props.config.ImageSettings.PreviewWidth}
|
||||
defaultValue={this.props.config.FileSettings.PreviewWidth}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'}</p>
|
||||
@@ -358,7 +358,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='PreviewHeight'
|
||||
ref='PreviewHeight'
|
||||
placeholder='Ex "0"'
|
||||
defaultValue={this.props.config.ImageSettings.PreviewHeight}
|
||||
defaultValue={this.props.config.FileSettings.PreviewHeight}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'}</p>
|
||||
@@ -379,7 +379,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='ProfileWidth'
|
||||
ref='ProfileWidth'
|
||||
placeholder='Ex "1024"'
|
||||
defaultValue={this.props.config.ImageSettings.ProfileWidth}
|
||||
defaultValue={this.props.config.FileSettings.ProfileWidth}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'Width of profile picture.'}</p>
|
||||
@@ -400,7 +400,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='ProfileHeight'
|
||||
ref='ProfileHeight'
|
||||
placeholder='Ex "0"'
|
||||
defaultValue={this.props.config.ImageSettings.ProfileHeight}
|
||||
defaultValue={this.props.config.FileSettings.ProfileHeight}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'Height of profile picture.'}</p>
|
||||
@@ -421,7 +421,7 @@ export default class ImageSettings extends React.Component {
|
||||
name='EnablePublicLink'
|
||||
value='true'
|
||||
ref='EnablePublicLink'
|
||||
defaultChecked={this.props.config.ImageSettings.EnablePublicLink}
|
||||
defaultChecked={this.props.config.FileSettings.EnablePublicLink}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{'true'}
|
||||
@@ -431,7 +431,7 @@ export default class ImageSettings extends React.Component {
|
||||
type='radio'
|
||||
name='EnablePublicLink'
|
||||
value='false'
|
||||
defaultChecked={!this.props.config.ImageSettings.EnablePublicLink}
|
||||
defaultChecked={!this.props.config.FileSettings.EnablePublicLink}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{'false'}
|
||||
@@ -454,7 +454,7 @@ export default class ImageSettings extends React.Component {
|
||||
id='PublicLinkSalt'
|
||||
ref='PublicLinkSalt'
|
||||
placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
|
||||
defaultValue={this.props.config.ImageSettings.PublicLinkSalt}
|
||||
defaultValue={this.props.config.FileSettings.PublicLinkSalt}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p className='help-text'>{'32-character salt added to signing of public image links.'}</p>
|
||||
@@ -491,6 +491,6 @@ export default class ImageSettings extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
ImageSettings.propTypes = {
|
||||
FileSettings.propTypes = {
|
||||
config: React.PropTypes.object
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ export default class ChannelHeader extends React.Component {
|
||||
if (!Utils.areStatesEqual(newState, this.state)) {
|
||||
this.setState(newState);
|
||||
}
|
||||
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}});
|
||||
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
|
||||
}
|
||||
onSocketChange(msg) {
|
||||
if (msg.action === 'new_user') {
|
||||
|
||||
@@ -12,6 +12,7 @@ var PostStore = require('../stores/post_store.jsx');
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
|
||||
var Utils = require('../utils/utils.jsx');
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
|
||||
export default class ChannelLoader extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -68,33 +69,19 @@ export default class ChannelLoader extends React.Component {
|
||||
/* Update CSS classes to match user theme */
|
||||
var user = UserStore.getCurrentUser();
|
||||
|
||||
if (user.props && user.props.theme) {
|
||||
Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
|
||||
Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
|
||||
Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
|
||||
Utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
|
||||
Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
|
||||
Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
|
||||
Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';');
|
||||
}
|
||||
|
||||
if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
|
||||
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';');
|
||||
Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
|
||||
} else if (user.props.theme === '#000000') {
|
||||
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';');
|
||||
$('.team__header').addClass('theme--black');
|
||||
} else if (user.props.theme === '#585858') {
|
||||
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';');
|
||||
$('.team__header').addClass('theme--gray');
|
||||
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
|
||||
Utils.applyTheme(user.theme_props);
|
||||
} else {
|
||||
Utils.applyTheme(Constants.THEMES.default);
|
||||
}
|
||||
|
||||
/* Setup global mouse events */
|
||||
$('body').on('click.userpopover', function popOver(e) {
|
||||
if ($(e.target).attr('data-toggle') !== 'popover' &&
|
||||
$(e.target).parents('.popover.in').length === 0) {
|
||||
$('.user-popover').popover('hide');
|
||||
}
|
||||
$('body').on('click', function hidePopover(e) {
|
||||
$('[data-toggle="popover"]').each(function eachPopover() {
|
||||
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
|
||||
$(this).popover('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class CreatePost extends React.Component {
|
||||
|
||||
this.lastTime = 0;
|
||||
|
||||
this.getCurrentDraft = this.getCurrentDraft.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
|
||||
this.handleUserInput = this.handleUserInput.bind(this);
|
||||
@@ -36,23 +37,15 @@ export default class CreatePost extends React.Component {
|
||||
|
||||
PostStore.clearDraftUploads();
|
||||
|
||||
const draft = PostStore.getCurrentDraft();
|
||||
let previews = [];
|
||||
let messageText = '';
|
||||
let uploadsInProgress = [];
|
||||
if (draft && draft.previews && draft.message) {
|
||||
previews = draft.previews;
|
||||
messageText = draft.message;
|
||||
uploadsInProgress = draft.uploadsInProgress;
|
||||
}
|
||||
const draft = this.getCurrentDraft();
|
||||
|
||||
this.state = {
|
||||
channelId: ChannelStore.getCurrentId(),
|
||||
messageText: messageText,
|
||||
uploadsInProgress: uploadsInProgress,
|
||||
previews: previews,
|
||||
messageText: draft.messageText,
|
||||
uploadsInProgress: draft.uploadsInProgress,
|
||||
previews: draft.previews,
|
||||
submitting: false,
|
||||
initialText: messageText
|
||||
initialText: draft.messageText
|
||||
};
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@@ -60,6 +53,24 @@ export default class CreatePost extends React.Component {
|
||||
this.resizePostHolder();
|
||||
}
|
||||
}
|
||||
getCurrentDraft() {
|
||||
const draft = PostStore.getCurrentDraft();
|
||||
const safeDraft = {previews: [], messageText: '', uploadsInProgress: []};
|
||||
|
||||
if (draft) {
|
||||
if (draft.message) {
|
||||
safeDraft.messageText = draft.message;
|
||||
}
|
||||
if (draft.previews) {
|
||||
safeDraft.previews = draft.previews;
|
||||
}
|
||||
if (draft.uploadsInProgress) {
|
||||
safeDraft.uploadsInProgress = draft.uploadsInProgress;
|
||||
}
|
||||
}
|
||||
|
||||
return safeDraft;
|
||||
}
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -253,18 +264,9 @@ export default class CreatePost extends React.Component {
|
||||
onChange() {
|
||||
const channelId = ChannelStore.getCurrentId();
|
||||
if (this.state.channelId !== channelId) {
|
||||
let draft = PostStore.getCurrentDraft();
|
||||
const draft = this.getCurrentDraft();
|
||||
|
||||
let previews = [];
|
||||
let messageText = '';
|
||||
let uploadsInProgress = [];
|
||||
if (draft && draft.previews && draft.message) {
|
||||
previews = draft.previews;
|
||||
messageText = draft.message;
|
||||
uploadsInProgress = draft.uploadsInProgress;
|
||||
}
|
||||
|
||||
this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress});
|
||||
this.setState({channelId: channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
|
||||
}
|
||||
}
|
||||
getFileCount(channelId) {
|
||||
|
||||
@@ -10,12 +10,14 @@ export default class EmailVerify extends React.Component {
|
||||
this.state = {};
|
||||
}
|
||||
handleResend() {
|
||||
window.location.href = window.location.href + '&resend=true';
|
||||
const newAddress = window.location.href.replace('&resend_success=true', '');
|
||||
window.location.href = newAddress + '&resend=true';
|
||||
}
|
||||
render() {
|
||||
var title = '';
|
||||
var body = '';
|
||||
var resend = '';
|
||||
var resendConfirm = '';
|
||||
if (this.props.isVerified === 'true') {
|
||||
title = global.window.config.SiteName + ' Email Verified';
|
||||
body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
|
||||
@@ -30,6 +32,9 @@ export default class EmailVerify extends React.Component {
|
||||
Resend Email
|
||||
</button>
|
||||
);
|
||||
if (this.props.resendSuccess) {
|
||||
resendConfirm = <div><br /><p className='alert alert-success'><i className='fa fa-check'></i>{' Verification email sent.'}</p></div>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -41,6 +46,7 @@ export default class EmailVerify extends React.Component {
|
||||
<div className='panel-body'>
|
||||
{body}
|
||||
{resend}
|
||||
{resendConfirm}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,10 +57,12 @@ export default class EmailVerify extends React.Component {
|
||||
EmailVerify.defaultProps = {
|
||||
isVerified: 'false',
|
||||
teamURL: '',
|
||||
userEmail: ''
|
||||
userEmail: '',
|
||||
resendSuccess: 'false'
|
||||
};
|
||||
EmailVerify.propTypes = {
|
||||
isVerified: React.PropTypes.string,
|
||||
teamURL: React.PropTypes.string,
|
||||
userEmail: React.PropTypes.string
|
||||
userEmail: React.PropTypes.string,
|
||||
resendSuccess: React.PropTypes.string
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ export default class MoreDirectChannels extends React.Component {
|
||||
<span aria-hidden='true'>×</span>
|
||||
<span className='sr-only'>Close</span>
|
||||
</button>
|
||||
<h4 className='modal-title'>More Private Messages</h4>
|
||||
<h4 className='modal-title'>More Direct Messages</h4>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
<ul className='nav nav-pills nav-stacked'>
|
||||
|
||||
@@ -93,6 +93,7 @@ export default class NewChannelModal extends React.Component {
|
||||
<span>
|
||||
<Modal
|
||||
show={this.props.show}
|
||||
bsSize='large'
|
||||
onHide={this.props.onModalDismissed}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
@@ -122,7 +123,7 @@ export default class NewChannelModal extends React.Component {
|
||||
/>
|
||||
{displayNameError}
|
||||
<p className='input__help dark'>
|
||||
{'Channel URL: ' + prettyTeamURL + this.props.channelData.name + ' ('}
|
||||
{'URL: ' + prettyTeamURL + this.props.channelData.name + ' ('}
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.props.onChangeURLPressed}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
const Utils = require('../utils/utils.jsx');
|
||||
var client = require('../utils/client.jsx');
|
||||
|
||||
export default class PasswordResetSendLink extends React.Component {
|
||||
@@ -15,8 +16,8 @@ export default class PasswordResetSendLink extends React.Component {
|
||||
e.preventDefault();
|
||||
var state = {};
|
||||
|
||||
var email = React.findDOMNode(this.refs.email).value.trim();
|
||||
if (!email) {
|
||||
var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
|
||||
if (!email || !Utils.isEmail(email)) {
|
||||
state.error = 'Please enter a valid email address.';
|
||||
this.setState(state);
|
||||
return;
|
||||
@@ -67,7 +68,7 @@ export default class PasswordResetSendLink extends React.Component {
|
||||
<p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p>
|
||||
<div className={formClass}>
|
||||
<input
|
||||
type='text'
|
||||
type='email'
|
||||
className='form-control'
|
||||
name='email'
|
||||
ref='email'
|
||||
|
||||
@@ -65,7 +65,7 @@ export default class PopoverListMembers extends React.Component {
|
||||
>
|
||||
{count}
|
||||
<span
|
||||
className='glyphicon glyphicon-user'
|
||||
className='fa fa-user'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class Post extends React.Component {
|
||||
|
||||
var post = this.props.post;
|
||||
client.createPost(post, post.channel_id,
|
||||
function success(data) {
|
||||
(data) => {
|
||||
AsyncClient.getPosts();
|
||||
|
||||
var channel = ChannelStore.get(post.channel_id);
|
||||
@@ -65,11 +65,11 @@ export default class Post extends React.Component {
|
||||
post: data
|
||||
});
|
||||
},
|
||||
function error() {
|
||||
() => {
|
||||
post.state = Constants.POST_FAILED;
|
||||
PostStore.updatePendingPost(post);
|
||||
this.forceUpdate();
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
post.state = Constants.POST_LOADING;
|
||||
@@ -81,8 +81,40 @@ export default class Post extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.sameRoot !== this.props.sameRoot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.sameUser !== this.props.sameUser) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.getCommentCount(nextProps) !== this.getCommentCount(this.props)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
getCommentCount(props) {
|
||||
const post = props.post;
|
||||
const parentPost = props.parentPost;
|
||||
const posts = props.posts;
|
||||
|
||||
let commentCount = 0;
|
||||
let commentRootId;
|
||||
if (parentPost) {
|
||||
commentRootId = post.root_id;
|
||||
} else {
|
||||
commentRootId = post.id;
|
||||
}
|
||||
for (let postId in posts) {
|
||||
if (posts[postId].root_id === commentRootId) {
|
||||
commentCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return commentCount;
|
||||
}
|
||||
render() {
|
||||
var post = this.props.post;
|
||||
var parentPost = this.props.parentPost;
|
||||
@@ -93,18 +125,7 @@ export default class Post extends React.Component {
|
||||
type = 'Comment';
|
||||
}
|
||||
|
||||
var commentCount = 0;
|
||||
var commentRootId;
|
||||
if (parentPost) {
|
||||
commentRootId = post.root_id;
|
||||
} else {
|
||||
commentRootId = post.id;
|
||||
}
|
||||
for (var postId in posts) {
|
||||
if (posts[postId].root_id === commentRootId) {
|
||||
commentCount += 1;
|
||||
}
|
||||
}
|
||||
const commentCount = this.getCommentCount(this.props);
|
||||
|
||||
var rootUser;
|
||||
if (this.props.sameRoot) {
|
||||
|
||||
@@ -35,7 +35,6 @@ export default class PostBody extends React.Component {
|
||||
|
||||
parseEmojis() {
|
||||
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
|
||||
global.window.emojify.run(React.findDOMNode(this.refs.message_span));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
@@ -326,8 +326,8 @@ export default class PostList extends React.Component {
|
||||
<strong><UserProfile userId={teammate.id} /></strong>
|
||||
</div>
|
||||
<p className='channel-intro-text'>
|
||||
{'This is the start of your private message history with ' + teammateName + '.'}<br/>
|
||||
{'Private messages and files shared here are not shown to people outside this area.'}
|
||||
{'This is the start of your direct message history with ' + teammateName + '.'}<br/>
|
||||
{'Direct messages and files shared here are not shown to people outside this area.'}
|
||||
</p>
|
||||
<a
|
||||
className='intro-links'
|
||||
@@ -346,7 +346,7 @@ export default class PostList extends React.Component {
|
||||
|
||||
return (
|
||||
<div className='channel-intro'>
|
||||
<p className='channel-intro-text'>{'This is the start of your private message history with this teammate. Private messages and files shared here are not shown to people outside this area.'}</p>
|
||||
<p className='channel-intro-text'>{'This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.'}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export default class PostListContainer extends React.Component {
|
||||
for (let i = 0; i <= this.state.postLists.length - 1; i++) {
|
||||
postListCtls.push(
|
||||
<PostList
|
||||
key={'postlistkey' + i}
|
||||
channelId={postLists[i]}
|
||||
isActive={postLists[i] === channelId}
|
||||
/>
|
||||
|
||||
@@ -228,7 +228,7 @@ export default class RegisterAppModal extends React.Component {
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
<span aria-hidden='true'>{'x'}</span>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
|
||||
@@ -56,7 +56,6 @@ export default class RhsComment extends React.Component {
|
||||
}
|
||||
parseEmojis() {
|
||||
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
|
||||
global.window.emojify.run(React.findDOMNode(this.refs.message_holder));
|
||||
}
|
||||
componentDidMount() {
|
||||
this.parseEmojis();
|
||||
@@ -114,14 +113,7 @@ export default class RhsComment extends React.Component {
|
||||
var ownerOptions;
|
||||
if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
|
||||
ownerOptions = (
|
||||
<div
|
||||
className='dropdown'
|
||||
onClick={
|
||||
function scroll() {
|
||||
$('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time').scrollTop() + 50);
|
||||
}
|
||||
}
|
||||
>
|
||||
<div className='dropdown'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
|
||||
@@ -65,6 +65,7 @@ export default class RhsHeaderPost extends React.Component {
|
||||
aria-label='Close'
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
<i className='fa fa-sign-out'/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,6 @@ export default class RhsRootPost extends React.Component {
|
||||
}
|
||||
parseEmojis() {
|
||||
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
|
||||
global.window.emojify.run(React.findDOMNode(this.refs.message_holder));
|
||||
}
|
||||
componentDidMount() {
|
||||
this.parseEmojis();
|
||||
@@ -54,7 +53,7 @@ export default class RhsRootPost extends React.Component {
|
||||
var channelName;
|
||||
if (channel) {
|
||||
if (channel.type === 'D') {
|
||||
channelName = 'Private Message';
|
||||
channelName = 'Direct Message';
|
||||
} else {
|
||||
channelName = channel.display_name;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export default class SearchResultsHeader extends React.Component {
|
||||
title='Close'
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
<i className='fa fa-sign-out'/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ export default class SearchResultsItem extends React.Component {
|
||||
if (channel) {
|
||||
channelName = channel.display_name;
|
||||
if (channel.type === 'D') {
|
||||
channelName = 'Private Message';
|
||||
channelName = 'Direct Message';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ export default class Sidebar extends React.Component {
|
||||
{privateChannelItems}
|
||||
</ul>
|
||||
<ul className='nav nav-pills nav-stacked'>
|
||||
<li><h4>Private Messages</h4></li>
|
||||
<li><h4>Direct Messages</h4></li>
|
||||
{directMessageItems}
|
||||
{directMessageMore}
|
||||
</ul>
|
||||
|
||||
@@ -6,6 +6,10 @@ var client = require('../utils/client.jsx');
|
||||
var utils = require('../utils/utils.jsx');
|
||||
|
||||
export default class SidebarRightMenu extends React.Component {
|
||||
componentDidMount() {
|
||||
$('.sidebar--left .dropdown-menu').perfectScrollbar();
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
const ChoosePage = require('./team_signup_choose_auth.jsx');
|
||||
const EmailSignUpPage = require('./team_signup_with_email.jsx');
|
||||
const SSOSignupPage = require('./team_signup_with_sso.jsx');
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
|
||||
export default class TeamSignUp extends React.Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var utils = require('../utils/utils.jsx');
|
||||
var Utils = require('../utils/utils.jsx');
|
||||
var client = require('../utils/client.jsx');
|
||||
var UserStore = require('../stores/user_store.jsx');
|
||||
var BrowserStore = require('../stores/browser_store.jsx');
|
||||
@@ -30,13 +30,26 @@ export default class SignupUserComplete extends React.Component {
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const providedEmail = React.findDOMNode(this.refs.email).value.trim();
|
||||
if (!providedEmail) {
|
||||
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils.isEmail(providedEmail)) {
|
||||
this.setState({nameError: '', emailError: 'Please enter a valid email address', passwordError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.user.email = providedEmail;
|
||||
|
||||
this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase();
|
||||
if (!this.state.user.username) {
|
||||
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
var usernameError = utils.isValidUsername(this.state.user.username);
|
||||
var usernameError = Utils.isValidUsername(this.state.user.username);
|
||||
if (usernameError === 'Cannot use a reserved word as a username.') {
|
||||
this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''});
|
||||
return;
|
||||
@@ -50,12 +63,6 @@ export default class SignupUserComplete extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.user.email = React.findDOMNode(this.refs.email).value.trim();
|
||||
if (!this.state.user.email) {
|
||||
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.user.password = React.findDOMNode(this.refs.password).value.trim();
|
||||
if (!this.state.user.password || this.state.user.password .length < 5) {
|
||||
this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''});
|
||||
|
||||
108
web/react/components/user_settings/custom_theme_chooser.jsx
Обычный файл
108
web/react/components/user_settings/custom_theme_chooser.jsx
Обычный файл
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var Constants = require('../../utils/constants.jsx');
|
||||
|
||||
export default class CustomThemeChooser extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onPickerChange = this.onPickerChange.bind(this);
|
||||
this.onInputChange = this.onInputChange.bind(this);
|
||||
this.pasteBoxChange = this.pasteBoxChange.bind(this);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
componentDidMount() {
|
||||
$('.color-picker').colorpicker().on('changeColor', this.onPickerChange);
|
||||
}
|
||||
onPickerChange(e) {
|
||||
const theme = this.props.theme;
|
||||
theme[e.target.id] = e.color.toHex();
|
||||
theme.type = 'custom';
|
||||
this.props.updateTheme(theme);
|
||||
}
|
||||
onInputChange(e) {
|
||||
const theme = this.props.theme;
|
||||
theme[e.target.parentNode.id] = e.target.value;
|
||||
theme.type = 'custom';
|
||||
this.props.updateTheme(theme);
|
||||
}
|
||||
pasteBoxChange(e) {
|
||||
const text = e.target.value;
|
||||
|
||||
if (text.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const colors = text.split(',');
|
||||
|
||||
const theme = {type: 'custom'};
|
||||
let index = 0;
|
||||
Constants.THEME_ELEMENTS.forEach((element) => {
|
||||
if (index < colors.length) {
|
||||
theme[element.id] = colors[index];
|
||||
}
|
||||
index++;
|
||||
});
|
||||
|
||||
this.props.updateTheme(theme);
|
||||
}
|
||||
render() {
|
||||
const theme = this.props.theme;
|
||||
|
||||
const elements = [];
|
||||
let colors = '';
|
||||
Constants.THEME_ELEMENTS.forEach((element) => {
|
||||
elements.push(
|
||||
<div className='col-sm-4 form-group'>
|
||||
<label className='custom-label'>{element.uiName}</label>
|
||||
<div
|
||||
className='input-group color-picker'
|
||||
id={element.id}
|
||||
>
|
||||
<input
|
||||
className='form-control'
|
||||
type='text'
|
||||
defaultValue={theme[element.id]}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
<span className='input-group-addon'><i></i></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
colors += theme[element.id] + ',';
|
||||
});
|
||||
|
||||
const pasteBox = (
|
||||
<div className='col-sm-12'>
|
||||
<label className='custom-label'>
|
||||
{'Copy and paste to share theme colors:'}
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control'
|
||||
value={colors}
|
||||
onChange={this.pasteBoxChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='row form-group'>
|
||||
{elements}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{pasteBox}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CustomThemeChooser.propTypes = {
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
updateTheme: React.PropTypes.func.isRequired
|
||||
};
|
||||
179
web/react/components/user_settings/import_theme_modal.jsx
Обычный файл
179
web/react/components/user_settings/import_theme_modal.jsx
Обычный файл
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
const UserStore = require('../../stores/user_store.jsx');
|
||||
const Utils = require('../../utils/utils.jsx');
|
||||
const Client = require('../../utils/client.jsx');
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
|
||||
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
|
||||
const Constants = require('../../utils/constants.jsx');
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
export default class ImportThemeModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.updateShow = this.updateShow.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputError: '',
|
||||
show: false
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
UserStore.addImportModalListener(this.updateShow);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
UserStore.removeImportModalListener(this.updateShow);
|
||||
}
|
||||
updateShow(show) {
|
||||
this.setState({show});
|
||||
}
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const text = React.findDOMNode(this.refs.input).value;
|
||||
|
||||
if (!this.isInputValid(text)) {
|
||||
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
|
||||
return;
|
||||
}
|
||||
|
||||
const colors = text.split(',');
|
||||
const theme = {type: 'custom'};
|
||||
|
||||
theme.sidebarBg = colors[0];
|
||||
theme.sidebarText = colors[5];
|
||||
theme.sidebarUnreadText = colors[5];
|
||||
theme.sidebarTextHoverBg = colors[4];
|
||||
theme.sidebarTextHoverColor = colors[5];
|
||||
theme.sidebarTextActiveBg = colors[2];
|
||||
theme.sidebarTextActiveColor = colors[3];
|
||||
theme.sidebarHeaderBg = colors[1];
|
||||
theme.sidebarHeaderTextColor = colors[5];
|
||||
theme.onlineIndicator = colors[6];
|
||||
theme.mentionBj = colors[7];
|
||||
theme.mentionColor = '#ffffff';
|
||||
theme.centerChannelBg = '#ffffff';
|
||||
theme.centerChannelColor = '#333333';
|
||||
theme.linkColor = '#2389d7';
|
||||
theme.buttonBg = '#26a970';
|
||||
theme.buttonColor = '#ffffff';
|
||||
|
||||
let user = UserStore.getCurrentUser();
|
||||
user.theme_props = theme;
|
||||
|
||||
Client.updateUser(user,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECIEVED_ME,
|
||||
me: data
|
||||
});
|
||||
|
||||
this.setState({show: false});
|
||||
Utils.applyTheme(theme);
|
||||
$('#user_settings').modal('show');
|
||||
},
|
||||
(err) => {
|
||||
var state = this.getStateFromStores();
|
||||
state.serverError = err;
|
||||
this.setState(state);
|
||||
}
|
||||
);
|
||||
}
|
||||
isInputValid(text) {
|
||||
if (text.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.indexOf(' ') !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.length > 0 && text.indexOf(',') === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.length > 0) {
|
||||
const colors = text.split(',');
|
||||
|
||||
if (colors.length !== 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
if (colors[i].length !== 7 && colors[i].length !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (colors[i].charAt(0) !== '#') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
handleChange(e) {
|
||||
if (this.isInputValid(e.target.value)) {
|
||||
this.setState({inputError: null});
|
||||
} else {
|
||||
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<span>
|
||||
<Modal
|
||||
show={this.state.show}
|
||||
onHide={() => this.setState({show: false})}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'Import Slack Theme'}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<form
|
||||
role='form'
|
||||
className='form-horizontal'
|
||||
>
|
||||
<Modal.Body>
|
||||
<p>
|
||||
{'To import a theme, go to a Slack team and look for “”Preferences” -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
|
||||
</p>
|
||||
<div className='form-group less'>
|
||||
<div className='col-sm-9'>
|
||||
<input
|
||||
ref='input'
|
||||
type='text'
|
||||
className='form-control'
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{this.state.inputError}
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-default'
|
||||
onClick={() => this.setState({show: false})}
|
||||
>
|
||||
{'Cancel'}
|
||||
</button>
|
||||
<button
|
||||
onClick={this.handleSubmit}
|
||||
type='submit'
|
||||
className='btn btn-primary'
|
||||
tabIndex='3'
|
||||
>
|
||||
{'Submit'}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</form>
|
||||
</Modal>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
55
web/react/components/user_settings/premade_theme_chooser.jsx
Обычный файл
55
web/react/components/user_settings/premade_theme_chooser.jsx
Обычный файл
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var Utils = require('../../utils/utils.jsx');
|
||||
var Constants = require('../../utils/constants.jsx');
|
||||
|
||||
export default class PremadeThemeChooser extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
render() {
|
||||
const theme = this.props.theme;
|
||||
|
||||
const premadeThemes = [];
|
||||
for (const k in Constants.THEMES) {
|
||||
if (Constants.THEMES.hasOwnProperty(k)) {
|
||||
const premadeTheme = $.extend(true, {}, Constants.THEMES[k]);
|
||||
|
||||
let activeClass = '';
|
||||
if (premadeTheme.type === theme.type) {
|
||||
activeClass = 'active';
|
||||
}
|
||||
|
||||
premadeThemes.push(
|
||||
<div className='col-sm-3 premade-themes'>
|
||||
<div
|
||||
className={activeClass}
|
||||
onClick={() => this.props.updateTheme(premadeTheme)}
|
||||
>
|
||||
<label>
|
||||
<img
|
||||
className='img-responsive'
|
||||
src={'/static/images/themes/' + premadeTheme.type.toLowerCase() + '.png'}
|
||||
/>
|
||||
<div className='theme-label'>{Utils.toTitleCase(premadeTheme.type)}</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='row'>
|
||||
{premadeThemes}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PremadeThemeChooser.propTypes = {
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
updateTheme: React.PropTypes.func.isRequired
|
||||
};
|
||||
@@ -2,64 +2,32 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
var UserStore = require('../../stores/user_store.jsx');
|
||||
var SettingItemMin = require('../setting_item_min.jsx');
|
||||
var SettingItemMax = require('../setting_item_max.jsx');
|
||||
var Client = require('../../utils/client.jsx');
|
||||
var Utils = require('../../utils/utils.jsx');
|
||||
|
||||
var ThemeColors = ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000'];
|
||||
const CustomThemeChooser = require('./custom_theme_chooser.jsx');
|
||||
const PremadeThemeChooser = require('./premade_theme_chooser.jsx');
|
||||
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
|
||||
const Constants = require('../../utils/constants.jsx');
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
export default class UserSettingsAppearance extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.submitTheme = this.submitTheme.bind(this);
|
||||
this.updateTheme = this.updateTheme.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleImportModal = this.handleImportModal.bind(this);
|
||||
|
||||
this.state = this.getStateFromStores();
|
||||
}
|
||||
getStateFromStores() {
|
||||
var user = UserStore.getCurrentUser();
|
||||
var theme = '#2389d7';
|
||||
if (ThemeColors != null) {
|
||||
theme = ThemeColors[0];
|
||||
}
|
||||
if (user.props && user.props.theme) {
|
||||
theme = user.props.theme;
|
||||
}
|
||||
|
||||
return {theme: theme.toLowerCase()};
|
||||
}
|
||||
submitTheme(e) {
|
||||
e.preventDefault();
|
||||
var user = UserStore.getCurrentUser();
|
||||
if (!user.props) {
|
||||
user.props = {};
|
||||
}
|
||||
user.props.theme = this.state.theme;
|
||||
|
||||
Client.updateUser(user,
|
||||
function success() {
|
||||
this.props.updateSection('');
|
||||
window.location.reload();
|
||||
}.bind(this),
|
||||
function fail(err) {
|
||||
var state = this.getStateFromStores();
|
||||
state.serverError = err;
|
||||
this.setState(state);
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
updateTheme(e) {
|
||||
var hex = Utils.rgb2hex(e.target.style.backgroundColor);
|
||||
this.setState({theme: hex.toLowerCase()});
|
||||
}
|
||||
handleClose() {
|
||||
this.setState({serverError: null});
|
||||
this.props.updateTab('general');
|
||||
this.originalTheme = this.state.theme;
|
||||
}
|
||||
componentDidMount() {
|
||||
UserStore.addChangeListener(this.onChange);
|
||||
|
||||
if (this.props.activeSection === 'theme') {
|
||||
$(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
||||
}
|
||||
@@ -72,8 +40,81 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
UserStore.removeChangeListener(this.onChange);
|
||||
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
||||
this.props.updateSection('');
|
||||
}
|
||||
getStateFromStores() {
|
||||
const user = UserStore.getCurrentUser();
|
||||
let theme = null;
|
||||
|
||||
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
|
||||
theme = user.theme_props;
|
||||
} else {
|
||||
theme = $.extend(true, {}, Constants.THEMES.default);
|
||||
}
|
||||
|
||||
let type = 'premade';
|
||||
if (theme.type === 'custom') {
|
||||
type = 'custom';
|
||||
}
|
||||
|
||||
return {theme, type};
|
||||
}
|
||||
onChange() {
|
||||
const newState = this.getStateFromStores();
|
||||
|
||||
if (!Utils.areStatesEqual(this.state, newState)) {
|
||||
this.setState(newState);
|
||||
}
|
||||
}
|
||||
submitTheme(e) {
|
||||
e.preventDefault();
|
||||
var user = UserStore.getCurrentUser();
|
||||
user.theme_props = this.state.theme;
|
||||
|
||||
Client.updateUser(user,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECIEVED_ME,
|
||||
me: data
|
||||
});
|
||||
|
||||
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
||||
this.props.updateTab('general');
|
||||
$('#user_settings').modal('hide');
|
||||
},
|
||||
(err) => {
|
||||
var state = this.getStateFromStores();
|
||||
state.serverError = err;
|
||||
this.setState(state);
|
||||
}
|
||||
);
|
||||
}
|
||||
updateTheme(theme) {
|
||||
this.setState({theme});
|
||||
Utils.applyTheme(theme);
|
||||
}
|
||||
updateType(type) {
|
||||
this.setState({type});
|
||||
}
|
||||
handleClose() {
|
||||
const state = this.getStateFromStores();
|
||||
state.serverError = null;
|
||||
|
||||
Utils.applyTheme(state.theme);
|
||||
|
||||
this.setState(state);
|
||||
|
||||
$('.ps-container.modal-body').scrollTop(0);
|
||||
$('.ps-container.modal-body').perfectScrollbar('update');
|
||||
$('#user_settings').modal('hide');
|
||||
}
|
||||
handleImportModal() {
|
||||
$('#user_settings').modal('hide');
|
||||
AppDispatcher.handleViewAction({
|
||||
type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
|
||||
value: true
|
||||
});
|
||||
}
|
||||
render() {
|
||||
var serverError;
|
||||
@@ -81,67 +122,73 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
serverError = this.state.serverError;
|
||||
}
|
||||
|
||||
var themeSection;
|
||||
var self = this;
|
||||
const displayCustom = this.state.type === 'custom';
|
||||
|
||||
if (ThemeColors != null) {
|
||||
if (this.props.activeSection === 'theme') {
|
||||
var themeButtons = [];
|
||||
|
||||
for (var i = 0; i < ThemeColors.length; i++) {
|
||||
themeButtons.push(
|
||||
<button
|
||||
key={ThemeColors[i] + 'key' + i}
|
||||
ref={ThemeColors[i]}
|
||||
type='button'
|
||||
className='btn btn-lg color-btn'
|
||||
style={{backgroundColor: ThemeColors[i]}}
|
||||
onClick={this.updateTheme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
var inputs = [];
|
||||
|
||||
inputs.push(
|
||||
<li
|
||||
key='themeColorSetting'
|
||||
className='setting-list-item'
|
||||
>
|
||||
<div
|
||||
className='btn-group'
|
||||
data-toggle='buttons-radio'
|
||||
>
|
||||
{themeButtons}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
themeSection = (
|
||||
<SettingItemMax
|
||||
title='Theme Color'
|
||||
inputs={inputs}
|
||||
submit={this.submitTheme}
|
||||
serverError={serverError}
|
||||
updateSection={function updateSection(e) {
|
||||
self.props.updateSection('');
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
themeSection = (
|
||||
<SettingItemMin
|
||||
title='Theme Color'
|
||||
describe={this.state.theme}
|
||||
updateSection={function updateSection() {
|
||||
self.props.updateSection('theme');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let custom;
|
||||
let premade;
|
||||
if (displayCustom) {
|
||||
custom = (
|
||||
<CustomThemeChooser
|
||||
theme={this.state.theme}
|
||||
updateTheme={this.updateTheme}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
premade = (
|
||||
<PremadeThemeChooser
|
||||
theme={this.state.theme}
|
||||
updateTheme={this.updateTheme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const themeUI = (
|
||||
<div className='section-max appearance-section'>
|
||||
<div className='col-sm-12'>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input type='radio'
|
||||
checked={!displayCustom}
|
||||
onChange={this.updateType.bind(this, 'premade')}
|
||||
>
|
||||
{'Theme Colors'}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
{premade}
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input type='radio'
|
||||
checked={displayCustom}
|
||||
onChange={this.updateType.bind(this, 'custom')}
|
||||
>
|
||||
{'Custom Theme'}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
{custom}
|
||||
<hr />
|
||||
{serverError}
|
||||
<a
|
||||
className='btn btn-sm btn-primary'
|
||||
href='#'
|
||||
onClick={this.submitTheme}
|
||||
>
|
||||
{'Submit'}
|
||||
</a>
|
||||
<a
|
||||
className='btn btn-sm theme'
|
||||
href='#'
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
{'Cancel'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='modal-header'>
|
||||
@@ -151,21 +198,28 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
<span aria-hidden='true'>×</span>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
ref='title'
|
||||
>
|
||||
<i className='modal-back'></i>Appearance Settings
|
||||
<i className='modal-back'></i>{'Appearance Settings'}
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>Appearance Settings</h3>
|
||||
<h3 className='tab-header'>{'Appearance Settings'}</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{themeSection}
|
||||
{themeUI}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
<br/>
|
||||
<a
|
||||
className='theme'
|
||||
onClick={this.handleImportModal}
|
||||
>
|
||||
{'Import from Slack'}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -176,6 +230,5 @@ UserSettingsAppearance.defaultProps = {
|
||||
};
|
||||
UserSettingsAppearance.propTypes = {
|
||||
activeSection: React.PropTypes.string,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ export default class DeveloperTab extends React.Component {
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
<span aria-hidden='true'>{'x'}</span>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
|
||||
@@ -60,7 +60,7 @@ export default class UserSettingsModal extends React.Component {
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
<span aria-hidden='true'>{'x'}</span>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
|
||||
@@ -241,7 +241,7 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={notifyActive[1]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'mention')}
|
||||
>
|
||||
Only for mentions and private messages
|
||||
Only for mentions and direct messages
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
@@ -277,7 +277,7 @@ export default class NotificationsTab extends React.Component {
|
||||
} else {
|
||||
let describe = '';
|
||||
if (this.state.notifyLevel === 'mention') {
|
||||
describe = 'Only for mentions and private messages';
|
||||
describe = 'Only for mentions and direct messages';
|
||||
} else if (this.state.notifyLevel === 'none') {
|
||||
describe = 'Never';
|
||||
} else {
|
||||
@@ -414,7 +414,7 @@ export default class NotificationsTab extends React.Component {
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div><br/>{'Email notifications are sent for mentions and private messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div>
|
||||
<div><br/>{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user