Этот коммит содержится в:
=Corey Hulen
2015-09-10 15:15:04 -07:00
родитель 1108ac5306 927b474005
Коммит 83b04181da
52 изменённых файлов: 1465 добавлений и 239 удалений

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

@@ -64,9 +64,14 @@ export default class ChannelHeader extends React.Component {
handleLeave() {
Client.leaveChannel(this.state.channel.id,
function handleLeaveSuccess() {
AppDispatcher.handleViewAction({
type: ActionTypes.LEAVE_CHANNEL,
id: this.state.channel.id
});
const townsquare = ChannelStore.getByName('town-square');
Utils.switchChannel(townsquare);
},
}.bind(this),
function handleLeaveError(err) {
AsyncClient.dispatchError(err, 'handleLeave');
}

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

@@ -17,6 +17,8 @@ export default class ChannelLoader extends React.Component {
constructor(props) {
super(props);
this.intervalId = null;
this.onSocketChange = this.onSocketChange.bind(this);
this.state = {};
@@ -35,10 +37,12 @@ export default class ChannelLoader extends React.Component {
PostStore.clearPendingPosts();
/* Set up interval functions */
setInterval(
this.intervalId = setInterval(
function pollStatuses() {
AsyncClient.getStatuses();
}, 30000);
},
30000
);
/* Device tracking setup */
var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
@@ -49,12 +53,12 @@ export default class ChannelLoader extends React.Component {
/* Set up tracking for whether the window is active */
window.isActive = true;
$(window).focus(function windowFocus() {
$(window).on('focus', function windowFocus() {
AsyncClient.updateLastViewedAt();
window.isActive = true;
});
$(window).blur(function windowBlur() {
$(window).on('blur', function windowBlur() {
window.isActive = false;
});
@@ -84,6 +88,54 @@ export default class ChannelLoader extends React.Component {
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');
}
/* 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('mouseenter mouseleave', '.post', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
$(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
} else {
$(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
}
});
$('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
$(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
} else {
$(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
}
});
/* Setup modal events */
$('.modal').on('show.bs.modal', function onShow() {
$('.modal-body').css('overflow-y', 'auto');
$('.modal-body').css('max-height', $(window).height() * 0.7);
});
}
componentWillUnmount() {
clearInterval(this.intervalId);
$(window).off('focus');
$(window).off('blur');
SocketStore.removeChangeListener(this.onSocketChange);
$('body').off('click.userpopover');
$('body').off('mouseenter mouseleave', '.post');
$('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
$('.modal').off('show.bs.modal');
}
onSocketChange(msg) {
if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {

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

@@ -55,6 +55,11 @@ export default class CreatePost extends React.Component {
initialText: messageText
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.previews.length !== this.state.previews.length) {
this.resizePostHolder();
}
}
handleSubmit(e) {
e.preventDefault();
@@ -310,25 +315,33 @@ export default class CreatePost extends React.Component {
>
<div className='post-create'>
<div className='post-create-body'>
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress}
onHeightChange={this.resizePostHolder}
messageText={this.state.messageText}
createMessage='Write a message...'
channelId={this.state.channelId}
id='post_textbox'
ref='textbox'
/>
<FileUpload
ref='fileUpload'
getFileCount={this.getFileCount}
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
postType='post'
channelId=''
/>
<div className='post-body__cell'>
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress}
onHeightChange={this.resizePostHolder}
messageText={this.state.messageText}
createMessage='Write a message...'
channelId={this.state.channelId}
id='post_textbox'
ref='textbox'
/>
<FileUpload
ref='fileUpload'
getFileCount={this.getFileCount}
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
postType='post'
channelId=''
/>
</div>
<a
className='send-button theme'
onClick={this.handleSubmit}
>
<i className='fa fa-paper-plane' />
</a>
</div>
<div className={postFooterClassName}>
{postError}

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

@@ -97,6 +97,7 @@ export default class FileAttachment extends React.Component {
var filename = this.props.filename;
var fileInfo = utils.splitFileLocation(filename);
var fileUrl = utils.getFileUrl(filename);
var type = utils.getFileType(fileInfo.ext);
var thumbnail;
@@ -150,14 +151,25 @@ export default class FileAttachment extends React.Component {
{thumbnail}
</a>
<div className='post-image__details'>
<div
<a
href={fileUrl}
download={filenameString}
data-toggle='tooltip'
title={filenameString}
className='post-image__name'
>
{trimmedFilename}
</div>
</a>
<div>
<a
href={fileUrl}
download={filenameString}
className='post-image__download'
>
<span
className='fa fa-download'
/>
</a>
<span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span>
<span className='post-image__size'>{fileSizeString}</span>
</div>

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

@@ -18,8 +18,8 @@ var ActionTypes = Constants.ActionTypes;
import {strings} from '../utils/config.js';
export default class PostList extends React.Component {
constructor() {
super();
constructor(props) {
super(props);
this.gotMorePosts = false;
this.scrolled = false;
@@ -27,6 +27,7 @@ export default class PostList extends React.Component {
this.seenNewMessages = false;
this.isUserScroll = true;
this.userHasSeenNew = false;
this.loadInProgress = false;
this.onChange = this.onChange.bind(this);
this.onTimeChange = this.onTimeChange.bind(this);
@@ -34,22 +35,19 @@ export default class PostList extends React.Component {
this.createChannelIntroMessage = this.createChannelIntroMessage.bind(this);
this.loadMorePosts = this.loadMorePosts.bind(this);
this.loadFirstPosts = this.loadFirstPosts.bind(this);
this.activate = this.activate.bind(this);
this.deactivate = this.deactivate.bind(this);
this.resize = this.resize.bind(this);
this.state = this.getStateFromStores();
this.state = this.getStateFromStores(props.channelId);
this.state.numToDisplay = Constants.POST_CHUNK_SIZE;
this.state.isFirstLoadComplete = false;
}
getStateFromStores() {
var channel = ChannelStore.getCurrent();
if (channel == null) {
channel = {};
}
var postList = PostStore.getCurrentPosts();
getStateFromStores(id) {
var postList = PostStore.getPosts(id);
if (postList != null) {
var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id);
var deletedPosts = PostStore.getUnseenDeletedPosts(id);
if (deletedPosts && Object.keys(deletedPosts).length > 0) {
for (var pid in deletedPosts) {
@@ -70,7 +68,7 @@ export default class PostList extends React.Component {
});
}
var pendingPostList = PostStore.getPendingPosts(channel.id);
var pendingPostList = PostStore.getPendingPosts(id);
if (pendingPostList) {
postList.order = pendingPostList.order.concat(postList.order);
@@ -82,43 +80,42 @@ export default class PostList extends React.Component {
}
}
var lastViewed = Number.MAX_VALUE;
if (ChannelStore.getCurrentMember() != null) {
lastViewed = ChannelStore.getCurrentMember().last_viewed_at;
}
return {
postList: postList,
channel: channel,
lastViewed: lastViewed
postList: postList
};
}
componentDidMount() {
if (this.props.isActive) {
this.activate();
this.loadFirstPosts(this.props.channelId);
}
}
componentWillUnmount() {
this.deactivate();
}
activate() {
this.gotMorePosts = false;
this.scrolled = false;
this.prevScrollTop = 0;
this.seenNewMessages = false;
this.isUserScroll = true;
this.userHasSeenNew = false;
PostStore.clearUnseenDeletedPosts(this.props.channelId);
PostStore.addChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange);
var postHolder = $('.post-list-holder-by-time');
$('.modal').on('show.bs.modal', function onShow() {
$('.modal-body').css('overflow-y', 'auto');
$('.modal-body').css('max-height', $(window).height() * 0.7);
});
$(window).resize(function resize() {
if ($('#create_post').length > 0) {
var height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
postHolder.css('height', height + 'px');
}
var postHolder = $(React.findDOMNode(this.refs.postlist));
$(window).on('resize.' + this.props.channelId, function resize() {
this.resize();
if (!this.scrolled) {
this.scrollToBottom();
}
}.bind(this));
postHolder.scroll(function scroll() {
postHolder.on('scroll', function scroll() {
var position = postHolder.scrollTop() + postHolder.height() + 14;
var bottom = postHolder[0].scrollHeight;
@@ -134,43 +131,31 @@ export default class PostList extends React.Component {
this.isUserScroll = true;
}.bind(this));
$('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');
}
});
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
$('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
$(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
} else {
$(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
}
});
$('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
$(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
} else {
$(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
$(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
}
});
this.scrollToBottom();
if (this.state.channel.id != null) {
this.loadFirstPosts(this.state.channel.id);
if (!this.state.isFirstLoadComplete) {
this.loadFirstPosts(this.props.channelId);
}
this.resize();
this.onChange();
this.scrollToBottom();
}
deactivate() {
PostStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onTimeChange);
SocketStore.removeChangeListener(this.onSocketChange);
$('body').off('click.userpopover');
$(window).off('resize.' + this.props.channelId);
var postHolder = $(React.findDOMNode(this.refs.postlist));
postHolder.off('scroll');
}
componentDidUpdate(prevProps, prevState) {
if (!this.props.isActive) {
return;
}
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
@@ -187,7 +172,7 @@ export default class PostList extends React.Component {
var firstPost = posts[order[0]] || {};
var isNewPost = oldOrder.indexOf(order[0]) === -1;
if (this.state.channel.id !== prevState.channel.id) {
if (this.props.isActive && !prevProps.isActive) {
this.scrollToBottom();
} else if (oldOrder.length === 0) {
this.scrollToBottom();
@@ -201,39 +186,45 @@ export default class PostList extends React.Component {
} else if (isNewPost &&
userId === firstPost.user_id &&
!utils.isComment(firstPost)) {
this.state.lastViewed = utils.getTimestamp();
this.scrollToBottom(true);
// the user clicked 'load more messages'
} else if (this.gotMorePosts) {
var lastPost = oldPosts[oldOrder[prevState.numToDisplay]];
$('#' + lastPost.id)[0].scrollIntoView();
this.gotMorePosts = false;
} else {
this.scrollTo(this.prevScrollTop);
}
}
componentWillUpdate() {
var postHolder = $('.post-list-holder-by-time');
var postHolder = $(React.findDOMNode(this.refs.postlist));
this.prevScrollTop = postHolder.scrollTop();
}
componentWillUnmount() {
PostStore.removeChangeListener(this.onChange);
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onTimeChange);
SocketStore.removeChangeListener(this.onSocketChange);
$('body').off('click.userpopover');
$('.modal').off('show.bs.modal');
componentWillReceiveProps(nextProps) {
if (nextProps.isActive === true && this.props.isActive === false) {
this.activate();
} else if (nextProps.isActive === false && this.props.isActive === true) {
this.deactivate();
}
}
resize() {
const postHolder = $(React.findDOMNode(this.refs.postlist));
if ($('#create_post').length > 0) {
const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
postHolder.css('height', height + 'px');
}
}
scrollTo(val) {
this.isUserScroll = false;
var postHolder = $('.post-list-holder-by-time');
var postHolder = $(React.findDOMNode(this.refs.postlist));
postHolder[0].scrollTop = val;
}
scrollToBottom(force) {
this.isUserScroll = false;
var postHolder = $('.post-list-holder-by-time');
if ($('#new_message')[0] && !this.userHasSeenNew && !force) {
$('#new_message')[0].scrollIntoView();
var postHolder = $(React.findDOMNode(this.refs.postlist));
if ($('#new_message_' + this.props.channelId)[0] && !this.userHasSeenNew && !force) {
$('#new_message_' + this.props.channelId)[0].scrollIntoView();
} else {
postHolder.addClass('hide-scroll');
postHolder[0].scrollTop = postHolder[0].scrollHeight;
@@ -241,34 +232,32 @@ export default class PostList extends React.Component {
}
}
loadFirstPosts(id) {
if (this.loadInProgress) {
return;
}
if (this.props.channelId == null) {
return;
}
this.loadInProgress = true;
Client.getPosts(
id,
PostStore.getLatestUpdate(id),
function success() {
this.loadInProgress = false;
this.setState({isFirstLoadComplete: true});
}.bind(this),
function fail() {
this.loadInProgress = false;
this.setState({isFirstLoadComplete: true});
}.bind(this)
);
}
onChange() {
var newState = this.getStateFromStores();
// Special case where the channel wasn't yet set in componentDidMount
if (!this.state.isFirstLoadComplete && this.state.channel.id == null && newState.channel.id != null) {
this.loadFirstPosts(newState.channel.id);
}
if (!utils.areStatesEqual(newState, this.state)) {
if (this.state.channel.id !== newState.channel.id) {
PostStore.clearUnseenDeletedPosts(this.state.channel.id);
this.userHasSeenNew = false;
newState.numToDisplay = Constants.POST_CHUNK_SIZE;
} else {
newState.lastViewed = this.state.lastViewed;
}
var newState = this.getStateFromStores(this.props.channelId);
if (!utils.areStatesEqual(newState.postList, this.state.postList)) {
this.setState(newState);
}
}
@@ -424,7 +413,7 @@ export default class PostList extends React.Component {
}
}
var members = ChannelStore.getCurrentExtraInfo().members;
var members = ChannelStore.getExtraInfo(channel.id).members;
for (var i = 0; i < members.length; i++) {
if (members[i].roles.indexOf('admin') > -1) {
return members[i].username;
@@ -488,6 +477,11 @@ export default class PostList extends React.Component {
var userId = UserStore.getCurrentId();
var renderedLastViewed = false;
var lastViewed = Number.MAX_VALUE;
if (ChannelStore.getMember(this.props.channelId) != null) {
lastViewed = ChannelStore.getMember(this.props.channelId).last_viewed_at;
}
var numToDisplay = this.state.numToDisplay;
if (order.length - 1 < numToDisplay) {
@@ -543,13 +537,13 @@ export default class PostList extends React.Component {
);
}
if (post.user_id !== userId && post.create_at > this.state.lastViewed && !renderedLastViewed) {
if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) {
renderedLastViewed = true;
// Temporary fix to solve ie10/11 rendering issue
let newSeparatorId = '';
if (!utils.isBrowserIE()) {
newSeparatorId = 'new_message';
newSeparatorId = 'new_message_' + this.props.channelId;
}
postCtls.push(
<div
@@ -577,7 +571,7 @@ export default class PostList extends React.Component {
var posts = this.state.postList.posts;
var order = this.state.postList.order;
var channelId = this.state.channel.id;
var channelId = this.props.channelId;
$(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...');
@@ -619,7 +613,7 @@ export default class PostList extends React.Component {
render() {
var order = [];
var posts;
var channel = this.state.channel;
var channel = ChannelStore.get(this.props.channelId);
if (this.state.postList != null) {
posts = this.state.postList.posts;
@@ -628,7 +622,7 @@ export default class PostList extends React.Component {
var moreMessages = <p className='beginning-messages-text'>Beginning of Channel</p>;
if (channel != null) {
if (order.length > this.state.numToDisplay) {
if (order.length >= this.state.numToDisplay) {
moreMessages = (
<a
ref='loadmore'
@@ -655,10 +649,15 @@ export default class PostList extends React.Component {
/>);
}
var activeClass = '';
if (!this.props.isActive) {
activeClass = 'inactive';
}
return (
<div
ref='postlist'
className='post-list-holder-by-time'
className={'post-list-holder-by-time ' + activeClass}
>
<div className='post-list__table'>
<div className='post-list__content'>
@@ -670,3 +669,12 @@ export default class PostList extends React.Component {
);
}
}
PostList.defaultProps = {
isActive: false,
channelId: null
};
PostList.propTypes = {
isActive: React.PropTypes.bool,
channelId: React.PropTypes.string
};

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

@@ -0,0 +1,62 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
const PostList = require('./post_list.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
export default class PostListContainer extends React.Component {
constructor() {
super();
this.onChange = this.onChange.bind(this);
this.onLeave = this.onLeave.bind(this);
let currentChannelId = ChannelStore.getCurrentId();
if (currentChannelId) {
this.state = {currentChannelId: currentChannelId, postLists: [currentChannelId]};
} else {
this.state = {currentChannelId: null, postLists: []};
}
}
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
ChannelStore.addLeaveListener(this.onLeave);
}
onChange() {
let channelId = ChannelStore.getCurrentId();
if (channelId === this.state.currentChannelId) {
return;
}
let postLists = this.state.postLists;
if (postLists.indexOf(channelId) === -1) {
postLists.push(channelId);
}
this.setState({currentChannelId: channelId, postLists: postLists});
}
onLeave(id) {
let postLists = this.state.postLists;
var index = postLists.indexOf(id);
if (index !== -1) {
postLists.splice(index, 1);
}
}
render() {
let postLists = this.state.postLists;
let channelId = this.state.currentChannelId;
let postListCtls = [];
for (let i = 0; i <= this.state.postLists.length - 1; i++) {
postListCtls.push(
<PostList
channelId={postLists[i]}
isActive={postLists[i] === channelId}
/>
);
}
return (
<div>{postListCtls}</div>
);
}
}

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

@@ -12,14 +12,18 @@ export default class SettingItemMin extends React.Component {
href='#'
onClick={this.props.updateSection}
>
Edit
<i className='fa fa-pencil'/>
{'Edit'}
</a>
</li>
);
}
return (
<ul className='section-min'>
<ul
className='section-min'
onClick={this.props.updateSection}
>
<li className='col-sm-10 section-title'>{this.props.title}</li>
{editButton}
<li className='col-sm-7 section-describe'>{this.props.describe}</li>

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

@@ -64,9 +64,9 @@ export default class SettingsUpload extends React.Component {
}
return (
<ul className='section-max'>
<li className='col-xs-12 section-title'>{this.props.title}</li>
<li className='col-xs-offset-3'>{this.props.helpText}</li>
<li className='col-xs-offset-3 col-xs-8'>
<li className='col-sm-12 section-title'>{this.props.title}</li>
<li className='col-sm-offset-3 col-sm-9'>{this.props.helpText}</li>
<li className='col-sm-offset-3 col-sm-9'>
<ul className='setting-list'>
<li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>

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

@@ -315,10 +315,12 @@ export default class Sidebar extends React.Component {
if (unread) {
titleClass = 'unread-title';
if (!this.firstUnreadChannel) {
this.firstUnreadChannel = channel.name;
if (channel.id !== activeId) {
if (!this.firstUnreadChannel) {
this.firstUnreadChannel = channel.name;
}
this.lastUnreadChannel = channel.name;
}
this.lastUnreadChannel = channel.name;
}
var badge = null;

94
web/react/components/team_export_tab.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,94 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../utils/client.jsx');
export default class TeamExportTab extends React.Component {
constructor(props) {
super(props);
this.state = {status: 'request', link: '', err: ''};
this.onExportSuccess = this.onExportSuccess.bind(this);
this.onExportFailure = this.onExportFailure.bind(this);
this.doExport = this.doExport.bind(this);
}
onExportSuccess(data) {
this.setState({status: 'ready', link: data.link, err: ''});
}
onExportFailure(e) {
this.setState({status: 'failure', link: '', err: e.message});
}
doExport() {
if (this.state.status === 'in-progress') {
return;
}
this.setState({status: 'in-progress'});
Client.exportTeam(this.onExportSuccess, this.onExportFailure);
}
render() {
var messageSection = '';
switch (this.state.status) {
case 'request':
messageSection = '';
break;
case 'in-progress':
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-spinner fa-pulse' />
{' Exporting...'}
</p>
);
break;
case 'ready':
messageSection = (
<p className='confirm-import alert alert-success'>
<i className='fa fa-check' />
{' Ready for '}
<a
href={this.state.link}
download={true}
>
{'download'}
</a>
</p>
);
break;
case 'failure':
messageSection = (
<p className='confirm-import alert alert-warning'>
<i className='fa fa-warning' />
{' Unable to export: ' + this.state.err}
</p>
);
break;
}
return (
<div
ref='wrapper'
className='user-settings'
>
<h3 className='tab-header'>{'Export'}</h3>
<div className='divider-dark first'/>
<ul className='section-max'>
<li className='col-xs-12 section-title'>{'Export your team'}</li>
<li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'>
<li className='setting-list-item'>
<a
className='btn btn-sm btn-primary btn-file sel-btn'
href='#'
onClick={this.doExport}
>
{'Export'}
</a>
</li>
</ul>
</li>
</ul>
<div className='divider-dark'/>
{messageSection}
</div>
);
}
}

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

@@ -34,13 +34,11 @@ export default class TeamImportTab extends React.Component {
render() {
var uploadHelpText = (
<div>
<br/>
Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.
<br/><br/>
The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.
<br/><br/>
<p>{'Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}</p>
<p>{'The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.'}</p>
</div>
);
var uploadSection = (
<SettingUpload
title='Import from Slack'
@@ -58,7 +56,7 @@ export default class TeamImportTab extends React.Component {
break;
case 'in-progress':
messageSection = (
<p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i> Importing...</p>
<p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i>{' Importing...'}</p>
);
break;
case 'done':
@@ -99,18 +97,18 @@ export default class TeamImportTab extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
<span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
><i className='modal-back'></i>Import</h4>
><i className='modal-back'></i>{'Import'}</h4>
</div>
<div
ref='wrapper'
className='user-settings'
>
<h3 className='tab-header'>Import</h3>
<h3 className='tab-header'>{'Import'}</h3>
<div className='divider-dark first'/>
{uploadSection}
<div className='divider-dark'/>

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

@@ -3,6 +3,7 @@
var TeamStore = require('../stores/team_store.jsx');
var ImportTab = require('./team_import_tab.jsx');
var ExportTab = require('./team_export_tab.jsx');
var FeatureTab = require('./team_feature_tab.jsx');
var GeneralTab = require('./team_general_tab.jsx');
var Utils = require('../utils/utils.jsx');
@@ -64,6 +65,13 @@ export default class TeamSettings extends React.Component {
</div>
);
break;
case 'export':
result = (
<div>
<ExportTab />
</div>
);
break;
default:
result = (
<div/>

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

@@ -36,6 +36,7 @@ export default class TeamSettingsModal extends React.Component {
let tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
tabs.push({name: 'export', uiName: 'Export', icon: 'glyphicon glyphicon-download'});
tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'});
return (

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

@@ -242,7 +242,7 @@ export default class Textbox extends React.Component {
const e = React.findDOMNode(this.refs.message);
const w = React.findDOMNode(this.refs.wrapper);
let prevHeight = $(e).height();
const prevHeight = $(e).height();
const lht = parseInt($(e).css('lineHeight'), 10);
const lines = e.scrollHeight / lht;
@@ -260,7 +260,7 @@ export default class Textbox extends React.Component {
$(w).css({height: 'auto'}).height(167);
}
if (prevHeight !== $(e).height()) {
if (prevHeight !== $(e).height() && this.props.onHeightChange) {
this.props.onHeightChange();
}
}
@@ -320,6 +320,6 @@ Textbox.propTypes = {
messageText: React.PropTypes.string.isRequired,
onUserInput: React.PropTypes.func.isRequired,
onKeyPress: React.PropTypes.func.isRequired,
onHeightChange: React.PropTypes.func.isRequired,
onHeightChange: React.PropTypes.func,
createMessage: React.PropTypes.string.isRequired
};

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

@@ -40,6 +40,7 @@ export default class UserSettings extends React.Component {
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
/>
</div>
);
@@ -86,4 +87,4 @@ UserSettings.propTypes = {
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func
};
};

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

@@ -238,7 +238,7 @@ export default class UserSettingsGeneralTab extends React.Component {
key='firstNameSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>First Name</label>
<label className='col-sm-5 control-label'>{'First Name'}</label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -255,7 +255,7 @@ export default class UserSettingsGeneralTab extends React.Component {
key='lastNameSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>Last Name</label>
<label className='col-sm-5 control-label'>{'Last Name'}</label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -267,6 +267,28 @@ export default class UserSettingsGeneralTab extends React.Component {
</div>
);
function notifClick(e) {
e.preventDefault();
this.updateSection('');
this.props.updateTab('notifications');
}
const notifLink = (
<a
href='#'
onClick={notifClick.bind(this)}
>
{'Notifications'}
</a>
);
const extraInfo = (
<span>
{'By default, you will receive mention notifications when someone types your first name. '}
{'Go to '} {notifLink} {'settings to change this default.'}
</span>
);
nameSection = (
<SettingItemMax
title='Full Name'
@@ -278,6 +300,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updateSection('');
e.preventDefault();
}.bind(this)}
extraInfo={extraInfo}
/>
);
} else {
@@ -326,6 +349,13 @@ export default class UserSettingsGeneralTab extends React.Component {
</div>
);
const extraInfo = (
<span>
{'Use Nickname for a name you might be called that is different from your first name and user name.'}
{'This is most often used when two or more people have similar sounding names and usernames.'}
</span>
);
nicknameSection = (
<SettingItemMax
title='Nickname'
@@ -337,6 +367,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updateSection('');
e.preventDefault();
}.bind(this)}
extraInfo={extraInfo}
/>
);
} else {
@@ -375,6 +406,8 @@ export default class UserSettingsGeneralTab extends React.Component {
</div>
);
const extraInfo = (<span>{'Pick something easy for teammates to recognize and recall.'}</span>);
usernameSection = (
<SettingItemMax
title='Username'
@@ -386,6 +419,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updateSection('');
e.preventDefault();
}.bind(this)}
extraInfo={extraInfo}
/>
);
} else {
@@ -404,13 +438,13 @@ export default class UserSettingsGeneralTab extends React.Component {
let helpText = <div>Email is used for notifications, and requires verification if changed.</div>;
if (!this.state.emailEnabled) {
helpText = <div className='text-danger'><br />Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.</div>;
helpText = <div className='setting-list__hint text-danger'>{'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}</div>;
}
inputs.push(
<div key='emailSetting'>
<div className='form-group'>
<label className='col-sm-5 control-label'>Primary Email</label>
<label className='col-sm-5 control-label'>{'Primary Email'}</label>
<div className='col-sm-7'>
<input
className='form-control'
@@ -492,18 +526,18 @@ export default class UserSettingsGeneralTab extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
<span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
<i className='modal-back'></i>
General Settings
{'General Settings'}
</h4>
</div>
<div className='user-settings'>
<h3 className='tab-header'>General Settings</h3>
<h3 className='tab-header'>{'General Settings'}</h3>
<div className='divider-dark first'/>
{nameSection}
<div className='divider-light'/>
@@ -524,5 +558,6 @@ export default class UserSettingsGeneralTab extends React.Component {
UserSettingsGeneralTab.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
activeSection: React.PropTypes.string
};