Hooking up external components

Этот коммит содержится в:
Christopher Speller
2015-10-30 14:29:15 -04:00
родитель ed68f2e901
Коммит b9a3ff74dd
11 изменённых файлов: 220 добавлений и 65 удалений

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

@@ -57,14 +57,14 @@
}, },
"EmailSettings": { "EmailSettings": {
"EnableSignUpWithEmail": true, "EnableSignUpWithEmail": true,
"SendEmailNotifications": true, "SendEmailNotifications": false,
"RequireEmailVerification": false, "RequireEmailVerification": false,
"FeedbackName": "", "FeedbackName": "",
"FeedbackEmail": "", "FeedbackEmail": "",
"SMTPUsername": "", "SMTPUsername": "",
"SMTPPassword": "", "SMTPPassword": "",
"SMTPServer": "localhost", "SMTPServer": "",
"SMTPPort": "25", "SMTPPort": "",
"ConnectionSecurity": "", "ConnectionSecurity": "",
"InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS", "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS",
"PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL", "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL",

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

@@ -176,6 +176,7 @@ export default class CreatePost extends React.Component {
PostStore.storePendingPost(post); PostStore.storePendingPost(post);
PostStore.storeDraft(channel.id, null); PostStore.storeDraft(channel.id, null);
PostStore.jumpPostListBottom();
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
Client.createPost(post, channel, Client.createPost(post, channel,

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

@@ -3,10 +3,9 @@
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
var UpdatingTimeSinceCounter = require('./updating_time_since_counter.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var Tooltip = ReactBootstrap.Tooltip;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class PostInfo extends React.Component { export default class PostInfo extends React.Component {
constructor(props) { constructor(props) {
@@ -144,21 +143,12 @@ export default class PostInfo extends React.Component {
var dropdown = this.createDropdown(); var dropdown = this.createDropdown();
let tooltip = <Tooltip id={post.id + 'tooltip'}>{`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}</Tooltip>;
return ( return (
<ul className='post-header post-info'> <ul className='post-header post-info'>
<li className='post-header-col'> <li className='post-header-col'>
<OverlayTrigger <UpdatingTimeSinceCounter
delayShow={500} eventTime={post.create_at}
container={this} />
placement='top'
overlay={tooltip}
>
<time className='post-profile-time'>
{utils.displayDateTime(post.create_at)}
</time>
</OverlayTrigger>
</li> </li>
<li className='post-header-col post-header__reply'> <li className='post-header-col post-header__reply'>
<div className='dropdown'> <div className='dropdown'>

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

@@ -25,10 +25,12 @@ export default class PostListContainer extends React.Component {
this.loadMorePostsTop = this.loadMorePostsTop.bind(this); this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.postsLoaded = this.postsLoaded.bind(this); this.postsLoaded = this.postsLoaded.bind(this);
this.postsLoadedFailure = this.postsLoadedFailure.bind(this); this.postsLoadedFailure = this.postsLoadedFailure.bind(this);
this.handlePostListJumpRequest = this.handlePostListJumpRequest.bind(this);
const currentChannelId = ChannelStore.getCurrentId(); const currentChannelId = ChannelStore.getCurrentId();
const state = { const state = {
scrollType: PostsView.SCROLL_TYPE_BOTTOM, scrollType: PostsView.SCROLL_TYPE_BOTTOM,
scrollPost: null,
numPostsToDisplay: Constants.POST_CHUNK_SIZE numPostsToDisplay: Constants.POST_CHUNK_SIZE
}; };
if (currentChannelId) { if (currentChannelId) {
@@ -51,11 +53,29 @@ export default class PostListContainer extends React.Component {
ChannelStore.addChangeListener(this.onChannelChange); ChannelStore.addChangeListener(this.onChannelChange);
ChannelStore.addLeaveListener(this.onChannelLeave); ChannelStore.addLeaveListener(this.onChannelLeave);
PostStore.addChangeListener(this.onPostsChange); PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostListJumpListener(this.handlePostListJumpRequest);
} }
componentWillUnmount() { componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange); ChannelStore.removeChangeListener(this.onChannelChange);
ChannelStore.removeLeaveListener(this.onChannelLeave); ChannelStore.removeLeaveListener(this.onChannelLeave);
PostStore.removeChangeListener(this.onPostsChange); PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostListJumpListener(this.handlePostListJumpRequest);
}
handlePostListJumpRequest(type, post) {
switch (type) {
case Constants.PostListJumpTypes.BOTTOM:
this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM});
break;
case Constants.PostListJumpTypes.POST:
this.setState({
scrollType: PostsView.SCROLL_TYPE_POST,
scrollPost: post
});
break;
case Constants.PostListJumpTypes.SIDEBAR_OPEN:
this.setState({scrollType: PostsView.SIDEBAR_OPEN});
break;
}
} }
onChannelChange() { onChannelChange() {
const postLists = Object.assign({}, this.state.postLists); const postLists = Object.assign({}, this.state.postLists);
@@ -70,7 +90,7 @@ export default class PostListContainer extends React.Component {
PostStore.clearUnseenDeletedPosts(channelId); PostStore.clearUnseenDeletedPosts(channelId);
let lastViewed = Number.MAX_VALUE; let lastViewed = Number.MAX_VALUE;
let member = ChannelStore.getMember(channelId); const member = ChannelStore.getMember(channelId);
if (member != null) { if (member != null) {
lastViewed = member.last_viewed_at; lastViewed = member.last_viewed_at;
} }
@@ -219,10 +239,11 @@ export default class PostListContainer extends React.Component {
isActive={isActive} isActive={isActive}
postList={postLists[i]} postList={postLists[i]}
scrollType={this.state.scrollType} scrollType={this.state.scrollType}
scrollPost={this.state.scrollPost}
postListScrolled={this.handlePostListScroll} postListScrolled={this.handlePostListScroll}
loadMorePostsTopClicked={this.loadMorePostsTop} loadMorePostsTopClicked={this.loadMorePostsTop}
numPostsToDisplay={this.state.numPostsToDisplay} numPostsToDisplay={this.state.numPostsToDisplay}
introText={channel ? createChannelIntroMessage(channel) : []} introText={channel ? createChannelIntroMessage(channel) : null}
messageSeparatorTime={this.state.currentLastViewed} messageSeparatorTime={this.state.currentLastViewed}
/> />
); );

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

@@ -10,11 +10,16 @@ export default class PostsView extends React.Component {
super(props); super(props);
this.handleScroll = this.handleScroll.bind(this); this.handleScroll = this.handleScroll.bind(this);
this.isAtBottom = this.isAtBottom.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this); this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.postsRerendered = this.postsRerendered.bind(this); this.postsRerendered = this.postsRerendered.bind(this);
this.createPosts = this.createPosts.bind(this); this.createPosts = this.createPosts.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this); this.updateScrolling = this.updateScrolling.bind(this);
this.handleResize = this.handleResize.bind(this); this.handleResize = this.handleResize.bind(this);
this.jumpToPostNode = null;
this.wasAtBottom = true;
this.scrollHeight = 0;
} }
static get SCROLL_TYPE_FREE() { static get SCROLL_TYPE_FREE() {
return 1; return 1;
@@ -22,9 +27,28 @@ export default class PostsView extends React.Component {
static get SCROLL_TYPE_BOTTOM() { static get SCROLL_TYPE_BOTTOM() {
return 2; return 2;
} }
static get SIDEBAR_OPEN() {
return 3;
}
isAtBottom() {
return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight);
}
handleScroll() { handleScroll() {
const atBottom = ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); // HACK FOR RHS -- REMOVE WHEN RHS DIES
this.props.postListScrolled(atBottom); const childNodes = this.refs.postlistcontent.childNodes;
for (let i = 0; i < childNodes.length; i++) {
// If the node is 1/3 down the page
if (childNodes[i].offsetTop > (this.refs.postlist.scrollTop + (this.refs.postlist.offsetHeight / 3))) {
this.jumpToPostNode = childNodes[i];
break;
}
}
this.wasAtBottom = this.isAtBottom();
// --- --------
this.props.postListScrolled(this.isAtBottom());
this.prevScrollHeight = this.refs.postlist.scrollHeight;
} }
loadMorePostsTop() { loadMorePostsTop() {
this.props.loadMorePostsTopClicked(); this.props.loadMorePostsTopClicked();
@@ -134,25 +158,53 @@ export default class PostsView extends React.Component {
return postCtls; return postCtls;
} }
scrollToBottom() { updateScrolling() {
if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) { if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
}); });
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPost) {
window.requestAnimationFrame(() => {
const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPost]);
postNode.scrollIntoView();
if (this.refs.postlist.scrollTop === postNode.offsetTop) {
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3);
} else {
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - postNode.offsetTop);
}
});
} else if (this.props.scrollType === PostsView.SIDEBAR_OPEN) {
// If we are at the bottom then stay there
if (this.wasAtBottom) {
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
} else {
window.requestAnimationFrame(() => {
this.jumpToPostNode.scrollIntoView();
if (this.refs.postlist.scrollTop === this.jumpToPostNode.offsetTop) {
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3);
} else {
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - this.jumpToPostNode.offsetTop);
}
});
}
} else if (this.refs.postlist.scrollHeight !== this.prevScrollHeight) {
window.requestAnimationFrame(() => {
this.refs.postlist.scrollTop += (this.refs.postlist.scrollHeight - this.prevScrollHeight);
});
} }
} }
handleResize() { handleResize() {
this.scrollToBottom(); this.updateScrolling();
} }
componentDidMount() { componentDidMount() {
this.scrollToBottom(); this.updateScrolling();
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
} }
componentDidUpdate() { componentDidUpdate() {
this.scrollToBottom(); this.updateScrolling();
} }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
if (this.props.isActive !== nextProps.isActive) { if (this.props.isActive !== nextProps.isActive) {
@@ -238,7 +290,7 @@ PostsView.defaultProps = {
PostsView.propTypes = { PostsView.propTypes = {
isActive: React.PropTypes.bool, isActive: React.PropTypes.bool,
postList: React.PropTypes.object.isRequired, postList: React.PropTypes.object,
scrollPost: React.PropTypes.string, scrollPost: React.PropTypes.string,
scrollType: React.PropTypes.number, scrollType: React.PropTypes.number,
postListScrolled: React.PropTypes.func.isRequired, postListScrolled: React.PropTypes.func.isRequired,

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

@@ -20,23 +20,48 @@ export default class SidebarRight extends React.Component {
this.onSelectedChange = this.onSelectedChange.bind(this); this.onSelectedChange = this.onSelectedChange.bind(this);
this.onSearchChange = this.onSearchChange.bind(this); this.onSearchChange = this.onSearchChange.bind(this);
this.doStrangeThings = this.doStrangeThings.bind(this);
this.state = getStateFromStores(); this.state = getStateFromStores();
} }
componentDidMount() { componentDidMount() {
SearchStore.addSearchChangeListener(this.onSearchChange); SearchStore.addSearchChangeListener(this.onSearchChange);
PostStore.addSelectedPostChangeListener(this.onSelectedChange); PostStore.addSelectedPostChangeListener(this.onSelectedChange);
this.doStrangeThings();
} }
componentWillUnmount() { componentWillUnmount() {
SearchStore.removeSearchChangeListener(this.onSearchChange); SearchStore.removeSearchChangeListener(this.onSearchChange);
PostStore.removeSelectedPostChangeListener(this.onSelectedChange); PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
} }
componentDidUpdate() { componentWillUpdate() {
if (this.plScrolledToBottom) { PostStore.jumpPostListSidebarOpen();
var postHolder = $('.post-list-holder-by-time').not('.inactive');
postHolder.scrollTop(postHolder[0].scrollHeight);
} else {
$('.top-visible-post')[0].scrollIntoView();
} }
doStrangeThings() {
// We should have a better way to do this stuff
// Hence the function name.
$('.inner__wrap').removeClass('.move--right');
$('.inner__wrap').addClass('move--left');
$('.sidebar--left').removeClass('move--right');
$('.sidebar--right').addClass('move--left');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
if (!(this.state.search_visible || this.state.post_right_visible)) {
$('.inner__wrap').removeClass('move--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
return (
<div></div>
);
}
/*setTimeout(() => {
$('.sidebar__overlay').fadeOut('200', () => {
$('.sidebar__overlay').remove();
});
}, 500);*/
}
componentDidUpdate() {
this.doStrangeThings();
} }
onSelectedChange(fromSearch) { onSelectedChange(fromSearch) {
var newState = getStateFromStores(fromSearch); var newState = getStateFromStores(fromSearch);
@@ -52,34 +77,6 @@ export default class SidebarRight extends React.Component {
} }
} }
render() { render() {
var postHolder = $('.post-list-holder-by-time').not('.inactive');
if (postHolder[0]) {
const position = postHolder.scrollTop() + postHolder.height() + 14;
const bottom = postHolder[0].scrollHeight;
this.plScrolledToBottom = position >= bottom;
} else {
this.plScrolledToBottom = true;
}
if (!(this.state.search_visible || this.state.post_right_visible)) {
$('.inner__wrap').removeClass('move--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
return (
<div></div>
);
}
$('.inner__wrap').removeClass('.move--right').addClass('move--left');
$('.sidebar--left').removeClass('move--right');
$('.sidebar--right').addClass('move--left');
$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
setTimeout(() => {
$('.sidebar__overlay').fadeOut('200', function fadeOverlay() {
$(this).remove();
});
}, 500);
var content = ''; var content = '';
if (this.state.search_visible) { if (this.state.search_visible) {

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

@@ -0,0 +1,50 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
var Utils = require('../utils/utils.jsx');
var Tooltip = ReactBootstrap.Tooltip;
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class UpdatingTimeSinceCounter extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.intervalId = setInterval(() => {
this.forceUpdate();
}, 30000);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
const displayDate = Utils.displayDate(this.props.eventTime);
const displayTime = Utils.displayTime(this.props.eventTime);
const tooltip = (
<Tooltip id={'time-since-tooltip-' + this.props.eventTime}>
{displayDate + ' at ' + displayTime}
</Tooltip>
);
return (
<OverlayTrigger
delayShow={400}
placement='top'
overlay={tooltip}
>
<time className='post-profile-time'>
{Utils.displayDateTime(this.props.eventTime)}
</time>
</OverlayTrigger>
);
}
}
UpdatingTimeSinceCounter.defaultProps = {
eventTime: 0
};
UpdatingTimeSinceCounter.propTypes = {
eventTime: React.PropTypes.number.isRequired
};

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

@@ -55,9 +55,7 @@ function setupChannelPage(props) {
); );
ReactDOM.render( ReactDOM.render(
<ChannelView <ChannelView/>,
/>,
document.getElementById('channel_view') document.getElementById('channel_view')
); );

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

@@ -14,6 +14,7 @@ var ActionTypes = Constants.ActionTypes;
var CHANGE_EVENT = 'change'; var CHANGE_EVENT = 'change';
var SELECTED_POST_CHANGE_EVENT = 'selected_post_change'; var SELECTED_POST_CHANGE_EVENT = 'selected_post_change';
var EDIT_POST_EVENT = 'edit_post'; var EDIT_POST_EVENT = 'edit_post';
var POST_LIST_JUMP_EVENT = 'post_list_jump';
class PostStoreClass extends EventEmitter { class PostStoreClass extends EventEmitter {
constructor() { constructor() {
@@ -31,6 +32,10 @@ class PostStoreClass extends EventEmitter {
this.addEditPostListener = this.addEditPostListener.bind(this); this.addEditPostListener = this.addEditPostListener.bind(this);
this.removeEditPostListener = this.removeEditPostListener.bind(this); this.removeEditPostListener = this.removeEditPostListener.bind(this);
this.emitPostListJump = this.emitPostListJump.bind(this);
this.addPostListJumpListener = this.addPostListJumpListener.bind(this);
this.removePostListJumpListener = this.removePostListJumpListener.bind(this);
this.getCurrentPosts = this.getCurrentPosts.bind(this); this.getCurrentPosts = this.getCurrentPosts.bind(this);
this.storePosts = this.storePosts.bind(this); this.storePosts = this.storePosts.bind(this);
this.pStorePosts = this.pStorePosts.bind(this); this.pStorePosts = this.pStorePosts.bind(this);
@@ -100,6 +105,30 @@ class PostStoreClass extends EventEmitter {
this.removeListener(EDIT_POST_EVENT, callback); this.removeListener(EDIT_POST_EVENT, callback);
} }
emitPostListJump(type, post) {
this.emit(POST_LIST_JUMP_EVENT, type, post);
}
addPostListJumpListener(callback) {
this.on(POST_LIST_JUMP_EVENT, callback);
}
removePostListJumpListener(callback) {
this.removeListener(POST_LIST_JUMP_EVENT, callback);
}
jumpPostListBottom() {
this.emitPostListJump(Constants.PostListJumpTypes.BOTTOM, null);
}
jumpPostListToPost(post) {
this.emitPostListJump(Constants.PostListJumpTypes.POST, post);
}
jumpPostListSidebarOpen() {
this.emitPostListJump(Constants.PostListJumpTypes.SIDEBAR_OPEN, null);
}
getCurrentPosts() { getCurrentPosts() {
var currentId = ChannelStore.getCurrentId(); var currentId = ChannelStore.getCurrentId();

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

@@ -350,5 +350,10 @@ module.exports = {
ruby: 'Ruby', ruby: 'Ruby',
java: 'Java', java: 'Java',
ini: 'ini' ini: 'ini'
},
PostListJumpTypes: {
BOTTOM: 1,
POST: 2,
SIDEBAR_OPEN: 3
} }
}; };

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

@@ -8,6 +8,18 @@ body {
background: $body-bg; background: $body-bg;
position: relative; position: relative;
height: 100%; height: 100%;
&.white {
background: #fff;
> .container-fluid {
overflow: auto;
}
.inner__wrap {
> .row.content {
min-height: 100%;
margin-bottom: -89px;
}
}
}
} }
.inner__wrap { .inner__wrap {