summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2015-09-10 15:15:04 -0700
committer=Corey Hulen <corey@hulen.com>2015-09-10 15:15:04 -0700
commit83b04181da84d0456dfa02b8d52953eb3fd3d7d1 (patch)
tree49510d8a6d4be6aeed9a86816cffcbc82d66b09b /web
parent1108ac53063bedcfe00647fa0577e91cf60555de (diff)
parent927b474005b9e6c03f7385f4d1a06626dd0450e3 (diff)
downloadchat-83b04181da84d0456dfa02b8d52953eb3fd3d7d1.tar.gz
chat-83b04181da84d0456dfa02b8d52953eb3fd3d7d1.tar.bz2
chat-83b04181da84d0456dfa02b8d52953eb3fd3d7d1.zip
merging
Diffstat (limited to 'web')
-rw-r--r--web/react/components/channel_header.jsx7
-rw-r--r--web/react/components/channel_loader.jsx60
-rw-r--r--web/react/components/create_post.jsx51
-rw-r--r--web/react/components/file_attachment.jsx16
-rw-r--r--web/react/components/post_list.jsx212
-rw-r--r--web/react/components/post_list_container.jsx62
-rw-r--r--web/react/components/setting_item_min.jsx8
-rw-r--r--web/react/components/setting_upload.jsx6
-rw-r--r--web/react/components/sidebar.jsx8
-rw-r--r--web/react/components/team_export_tab.jsx94
-rw-r--r--web/react/components/team_import_tab.jsx16
-rw-r--r--web/react/components/team_settings.jsx8
-rw-r--r--web/react/components/team_settings_modal.jsx1
-rw-r--r--web/react/components/textbox.jsx6
-rw-r--r--web/react/components/user_settings.jsx3
-rw-r--r--web/react/components/user_settings_general.jsx49
-rw-r--r--web/react/pages/channel.jsx4
-rw-r--r--web/react/stores/channel_store.jsx14
-rw-r--r--web/react/utils/client.jsx13
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/utils.jsx2
-rw-r--r--web/sass-files/sass/partials/_files.scss12
-rw-r--r--web/sass-files/sass/partials/_modal.scss16
-rw-r--r--web/sass-files/sass/partials/_post.scss28
-rw-r--r--web/sass-files/sass/partials/_responsive.scss70
-rw-r--r--web/sass-files/sass/partials/_settings.scss36
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss6
-rw-r--r--web/web.go1
28 files changed, 626 insertions, 184 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 87b9cab04..db23a5831 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -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');
}
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 8e8ed3f73..ce6f60f87 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -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()) {
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 871b72a43..d9e67836d 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -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}
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index 78693df98..c9aa06a97 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -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>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 6fa87ca4a..9d95887d9 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -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');
- }
- });
+ if (!this.state.isFirstLoadComplete) {
+ this.loadFirstPosts(this.props.channelId);
+ }
+ this.resize();
+ this.onChange();
this.scrollToBottom();
-
- if (this.state.channel.id != null) {
- this.loadFirstPosts(this.state.channel.id);
- }
+ }
+ 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
+};
diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx
new file mode 100644
index 000000000..0815ac883
--- /dev/null
+++ b/web/react/components/post_list_container.jsx
@@ -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>
+ );
+ }
+}
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 098729a4f..2c0fdf2f4 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -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>
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
index 5979091c4..fad27b355 100644
--- a/web/react/components/setting_upload.jsx
+++ b/web/react/components/setting_upload.jsx
@@ -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'>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 983260187..ad934d271 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -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;
diff --git a/web/react/components/team_export_tab.jsx b/web/react/components/team_export_tab.jsx
new file mode 100644
index 000000000..2914904ad
--- /dev/null
+++ b/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>
+ );
+ }
+}
diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx
index 1ab348465..031abc36a 100644
--- a/web/react/components/team_import_tab.jsx
+++ b/web/react/components/team_import_tab.jsx
@@ -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'/>
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 53855fe1c..396521af9 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -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/>
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 668bf76cf..0513c811f 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -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 (
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index b4518fe80..ea8126bec 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -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
};
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 282fb7681..2a607b3e0 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -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
-}; \ No newline at end of file
+};
diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx
index ead7ac1d5..184534a9a 100644
--- a/web/react/components/user_settings_general.jsx
+++ b/web/react/components/user_settings_general.jsx
@@ -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
};
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 71a03cde0..182721bef 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -5,7 +5,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Navbar = require('../components/navbar.jsx');
var Sidebar = require('../components/sidebar.jsx');
var ChannelHeader = require('../components/channel_header.jsx');
-var PostList = require('../components/post_list.jsx');
+var PostListContainer = require('../components/post_list_container.jsx');
var CreatePost = require('../components/create_post.jsx');
var SidebarRight = require('../components/sidebar_right.jsx');
var SidebarRightMenu = require('../components/sidebar_right_menu.jsx');
@@ -159,7 +159,7 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
);
React.render(
- <PostList />,
+ <PostListContainer />,
document.getElementById('post-list')
);
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index bd655b767..b9ba37c27 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -10,6 +10,7 @@ var ActionTypes = Constants.ActionTypes;
var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
+var LEAVE_EVENT = 'leave';
var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info';
@@ -48,6 +49,15 @@ class ChannelStoreClass extends EventEmitter {
removeExtraInfoChangeListener(callback) {
this.removeListener(EXTRA_INFO_EVENT, callback);
}
+ emitLeave(id) {
+ this.emit(LEAVE_EVENT, id);
+ }
+ addLeaveListener(callback) {
+ this.on(LEAVE_EVENT, callback);
+ }
+ removeLeaveListener(callback) {
+ this.removeListener(LEAVE_EVENT, callback);
+ }
findFirstBy(field, value) {
var channels = this.pGetChannels();
for (var i = 0; i < channels.length; i++) {
@@ -272,6 +282,10 @@ ChannelStore.dispatchToken = AppDispatcher.register(function handleAction(payloa
ChannelStore.emitExtraInfoChange();
break;
+ case ActionTypes.LEAVE_CHANNEL:
+ ChannelStore.emitLeave(action.id);
+ break;
+
default:
break;
}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 10f9c0b37..51fd16474 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -919,6 +919,19 @@ export function importSlack(fileData, success, error) {
});
}
+export function exportTeam(success, error) {
+ $.ajax({
+ url: '/api/v1/teams/export_team',
+ type: 'GET',
+ dataType: 'json',
+ success: success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('exportTeam', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function getStatuses(success, error) {
$.ajax({
url: '/api/v1/users/status',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 18b7ff59c..7ead079d7 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -9,6 +9,7 @@ module.exports = {
CLICK_CHANNEL: null,
CREATE_CHANNEL: null,
+ LEAVE_CHANNEL: null,
RECIEVED_CHANNELS: null,
RECIEVED_CHANNEL: null,
RECIEVED_MORE_CHANNELS: null,
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index ea42256aa..71cd1d344 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -498,7 +498,7 @@ export function textToJsx(textin, options) {
// do both a non-case sensitive and case senstive check
let mClass = '';
- if (('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
+ if (implicitKeywords.indexOf('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
mClass = mentionClass;
}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 70f440989..405265f92 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -61,8 +61,8 @@
cursor: pointer;
z-index: 5;
opacity: inherit;
- text-shadow: 0 0px 3px #444;
- text-shadow: 0 0px 3px rgba(0, 0, 0, 0.7);
+ text-shadow: 0 0px 3px #444;
+ text-shadow: 0 0px 3px rgba(0, 0, 0, 0.7);
}
}
}
@@ -159,6 +159,14 @@
padding: 7px;
.post-image__name {
margin-bottom: 3px;
+ display: block;
+ color: #333;
+ }
+ .post-image__download {
+ display: inline-block;
+ padding-right: 7px;
+ cursor: pointer;
+ color: #555;
}
.post-image__type {
color: grey;
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index dec08b567..d32306cbc 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -63,7 +63,6 @@
margin: 0;
}
button.close {
- margin: -2px -2px 0 0;
color: #fff;
@include opacity(1);
z-index: 5;
@@ -71,7 +70,8 @@
height: 30px;
line-height: 30px;
@include single-transition(all, 0.25s, ease-in);
- position: relative;
+ position: absolute;
+ right: 10px;
&:hover {
background: rgba(0, 0, 0, 0.1);
}
@@ -82,7 +82,7 @@
.btn {
&.btn-primary {
float: right;
- margin-top: -4px;
+ margin: -4px 25px 0 0;
position: relative;
i {
margin-right: 5px;
@@ -118,12 +118,11 @@
}
&#more_channels {
.modal-body {
- padding: 5px 10px;
+ padding: 0;
}
}
.more-channel-table {
margin: 0;
- table-layout: fixed;
p {
font-size: 0.9em;
overflow: hidden;
@@ -138,16 +137,23 @@
}
tbody {
> tr {
+ &:hover td {
+ background: #f7f7f7;
+ }
&:first-child {
td {
border: none;
}
}
td {
+ width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ padding: 8px 8px 8px 15px;
&.td--action {
+ text-align: right;
+ padding: 8px 15px 8px 8px;
width: 70px;
vertical-align: middle;
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 0605e9c3b..6940cf2fb 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -139,9 +139,16 @@ body.ios {
width: 100%;
padding: 1em 0 0;
position: relative;
- &.hide-scroll::-webkit-scrollbar {
+ -webkit-overflow-scrolling: touch;
+ &::-webkit-scrollbar {
width: 0px !important;
}
+ &.inactive {
+ display: none;
+ }
+ &.active {
+ display: inline;
+ }
}
.post-list__table {
display: table;
@@ -181,6 +188,25 @@ body.ios {
max-width: 850px;
padding: 0 0 2px;
position: relative;
+ .post-body__cell {
+ vertical-align: top;
+ position: relative;
+ }
+ .send-button {
+ display: none;
+ cursor: pointer;
+ padding-right: 4px;
+ width: 45px;
+ height: 37px;
+ font-size: 18px;
+ line-height: 37px;
+ vertical-align: bottom;
+ text-align: center;
+ @include single-transition(all, 0.15s);
+ &:active {
+ @include opacity(0.75);
+ }
+ }
.custom-textarea {
padding-top: 8px;
padding-right: 28px;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index f691fba43..a30782dd0 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -243,6 +243,23 @@
}
}
@media screen and (max-width: 768px) {
+ .file-details__container {
+ display: block;
+ .file-details__preview {
+ display: block;
+ width: 100%;
+ height: 150px;
+ border-right: none;
+ border-bottom: 1px solid #ddd;
+ img {
+ width: 64px;
+ height: 64px;
+ }
+ }
+ .file-details {
+ height: auto;
+ }
+ }
.center-file-overlay {
font-size: 1.3em;
}
@@ -348,6 +365,9 @@
width: 100%;
z-index: 5;
}
+ .modal-title {
+ padding-left: 20px;
+ }
.user-settings {
.tab-header {
display: none;
@@ -392,9 +412,35 @@
}
}
}
- #post-create {
+ .post-create__container {
+ .post-right__container & {
+ padding: 0 1em;
+ }
+ form {
+ padding: 0;
+ }
.post-create-body {
padding-bottom: 10px;
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ .post-body__cell {
+ display: table-cell;
+ padding-left: 45px;
+ }
+ .app__content & {
+ .btn-file {
+ width: 45px;
+ padding: 0;
+ line-height: 36px;
+ bottom: -2px;
+ left: 0;
+ top: auto;
+ }
+ }
+ .send-button {
+ display: table-cell;
+ }
}
.post-create-footer .msg-typing {
display: none;
@@ -405,23 +451,23 @@
margin-top: 0;
}
.remove-preview {
- width: 50px;
- height: 50px;
- left: 50%;
- top: 50%;
+ width: 28px;
+ height: 28px;
+ left: auto;
+ right: 0;
+ top: 0;
background: #444;
- margin: -25px 0 0 -25px;
- @include border-radius(50px);
+ background: rgba(#000, 0.5);
text-align: center;
&:after {
display: none;
}
i {
- line-height: 50px;
+ line-height: 29px;
top: auto;
right: auto;
position: relative;
- font-size: 28px;
+ font-size: 16px;
}
}
}
@@ -496,6 +542,12 @@
height: 45px;
position: relative;
@include single-transition(all, 0.2s, linear);
+ .glyphicon-refresh-animate {
+ right: 33px;
+ top: 15px;
+ color: #fff;
+ color: rgba(255,255,255,0.5);
+ }
.form-control {
border: none;
padding: 0 10px 0 31px;
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 99a7eb7bc..2b59a943b 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -14,13 +14,15 @@
width:800px;
max-width: 100%;
.modal-back {
- width: 8px;
- height: 13px;
- background: url("../images/arrow-left.png");
- @include background-size(100% 100%);
- margin-right: 10px;
- display: inline-block;
+ width: 40px;
+ height: 56px;
+ background: url("../images/arrow-left.png") no-repeat;
+ @include background-size(8px 13px);
+ background-position: center;
+ top: 0;
+ left: 0;
cursor: pointer;
+ position: absolute;
}
.modal-body {
padding: 0;
@@ -54,12 +56,22 @@
.section-min {
padding: 1em 0;
margin-bottom: 0;
+ cursor: pointer;
@include clearfix;
+ &:hover {
+ background: #f9f9f9;
+ }
+ &:hover .fa {
+ display: inline-block;
+ }
+ &:hover .section-edit {
+ text-decoration: underline;
+ }
}
.section-max {
background: #f2f2f2;
- padding: 1em 0;
+ padding: 1em 0 1.3em;
margin-bottom: 0;
@include clearfix;
.section-title {
@@ -75,6 +87,12 @@
.section-edit {
text-align: right;
margin-bottom: 5px;
+ .fa {
+ margin-right: 7px;
+ font-size: 12px;
+ color: #aaa;
+ display: none;
+ }
}
.section-describe {
@@ -94,6 +112,10 @@
list-style-type:none;
}
+ .setting-list__hint {
+ margin-top: 20px;
+ }
+
.mentions-input {
margin-top: 10px;
}
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index 6b827eaee..d4ed41130 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -10,6 +10,10 @@
&.sidebar--padded {
padding-top: 44px;
}
+ .dropdown-menu {
+ max-height: 300px;
+ overflow: auto;
+ }
.search__form {
margin: 0;
padding: 1em 1em 0;
@@ -46,7 +50,7 @@
right: 0;
width: 72%;
color: #777;
- background: #DCF0FF;
+ background: #2389D7;
@include border-radius(50px);
margin: 0 auto;
padding: 3px 0 4px;
diff --git a/web/web.go b/web/web.go
index 44c9610a6..9cb81226b 100644
--- a/web/web.go
+++ b/web/web.go
@@ -560,6 +560,7 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
}
user.TeamId = team.Id
+ user.EmailVerified = true
ruser := api.CreateUser(c, team, user)
if c.Err != nil {