Progress on DMs responsiveness
Progress on DMs responsiveness
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
|
||||
import ChatConversationsSearch from './chat_conversations_search'
|
||||
import ChatConversationsList from './chat_conversations_list'
|
||||
import ChatConversationRequestsListItem from './chat_conversations_requests_list_item'
|
||||
|
||||
class ChatApprovedConversationsSidebar extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
const { source } = this.props
|
||||
|
||||
return (
|
||||
<ResponsiveClassesComponent
|
||||
classNames={[_s.d, _s.w340PX, _s.h100PC, _s.bgPrimary, _s.borderLeft1PX, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}
|
||||
classNamesSmall={[_s.d, _s.w300PX, _s.h100PC, _s.bgPrimary, _s.borderLeft1PX, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}
|
||||
classNamesXS={[_s.d, _s.w100PC, _s.h100PC, _s.overflowYScroll, _s.bgPrimary].join(' ')}
|
||||
>
|
||||
<div className={[_s.d, _s.h100PC, _s.overflowHidden, _s.w100PC, _s.boxShadowNone].join(' ')}>
|
||||
<ChatConversationsSearch />
|
||||
<ResponsiveClassesComponent
|
||||
classNames={[_s.d, _s.w100PC, _s.posAbs, _s.bottom0, _s.top60PX, _s.overflowYScroll].join(' ')}
|
||||
classNamesXS={[_s.d, _s.w100PC].join(' ')}
|
||||
>
|
||||
<ChatConversationRequestsListItem />
|
||||
<ChatConversationsList source={source} />
|
||||
</ResponsiveClassesComponent>
|
||||
</div>
|
||||
</ResponsiveClassesComponent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ChatApprovedConversationsSidebar.propTypes = {
|
||||
source: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ChatApprovedConversationsSidebar
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { openPopover } from '../../../actions/popover'
|
||||
import { approveChatConversationRequest } from '../../../actions/chat_conversations'
|
||||
import {
|
||||
POPOVER_CHAT_CONVERSATION_OPTIONS
|
||||
} from '../../../constants'
|
||||
import Button from '../../../components/button'
|
||||
import Text from '../../../components/text'
|
||||
|
||||
class ChatConversationRequestApproveBar extends React.PureComponent {
|
||||
|
||||
handleOnApproveMessageRequest = () => {
|
||||
this.props.onApproveChatConversationRequest(this.props.chatConversationId)
|
||||
}
|
||||
|
||||
setOptionsBtnRef = (c) => {
|
||||
this.optionsBtnRef = c
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={[_s.d, _s.z4, _s.minH53PX, _s.w100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.minH53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.bottom0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
|
||||
<div className={[_s.d, _s.w100PC, _s.pt15, _s.px15, _s.aiCenter, _s.jcCenter, _s.saveAreaInsetPB, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.w100PC].join(' ')}>
|
||||
<Button
|
||||
isNarrow
|
||||
onClick={this.handleOnApproveMessageRequest}
|
||||
>
|
||||
<Text color='inherit' align='center'>
|
||||
Approve Message Request
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onApproveChatConversationRequest(chatConversationId) {
|
||||
dispatch(approveChatConversationRequest(chatConversationId))
|
||||
},
|
||||
onOpenChatConversationOptionsPopover(chatConversationId, targetRef) {
|
||||
dispatch(openPopover(POPOVER_CHAT_CONVERSATION_OPTIONS, {
|
||||
chatConversationId,
|
||||
targetRef,
|
||||
position: 'bottom',
|
||||
}))
|
||||
},
|
||||
})
|
||||
|
||||
ChatConversationRequestApproveBar.propTypes = {
|
||||
chatConversationId: PropTypes.string,
|
||||
onApproveChatConversationRequest: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ChatConversationRequestApproveBar)
|
||||
@@ -28,55 +28,51 @@ class ChatMessagesComposeForm extends React.PureComponent {
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
this.setState({ focused: false });
|
||||
this.setState({ focused: false })
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
this.setState({ focused: true });
|
||||
this.setState({ focused: true })
|
||||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
const { disabled } = this.props;
|
||||
const { disabled } = this.props
|
||||
|
||||
if (disabled) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (disabled) return e.preventDefault()
|
||||
|
||||
// Ignore key events during text composition
|
||||
// e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
|
||||
if (e.which === 229 || e.isComposing) return;
|
||||
if (e.which === 229) return
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
document.querySelector('#gabsocial').focus()
|
||||
break;
|
||||
break
|
||||
case 'Enter':
|
||||
this.handleOnSendChatMessage()
|
||||
return e.preventDefault()
|
||||
case 'Tab':
|
||||
//
|
||||
break;
|
||||
this.sendBtn.focus()
|
||||
return e.preventDefault()
|
||||
break
|
||||
}
|
||||
|
||||
// if (e.defaultPrevented || !this.props.onKeyDown) return;
|
||||
if (e.defaultPrevented) return
|
||||
}
|
||||
|
||||
setTextbox = (c) => {
|
||||
this.textbox = c
|
||||
}
|
||||
|
||||
setSendBtn = (c) => {
|
||||
this.sendBtn = c
|
||||
}
|
||||
|
||||
render () {
|
||||
const { chatConversationId } = this.props
|
||||
const { isXS, chatConversationId } = this.props
|
||||
const { value } = this.state
|
||||
const disabled = false
|
||||
|
||||
const textareaContainerClasses = CX({
|
||||
d: 1,
|
||||
maxW100PC: 1,
|
||||
flexGrow1: 1,
|
||||
jcCenter: 1,
|
||||
py5: 1,
|
||||
})
|
||||
|
||||
const textareaClasses = CX({
|
||||
d: 1,
|
||||
font: 1,
|
||||
@@ -95,31 +91,59 @@ class ChatMessagesComposeForm extends React.PureComponent {
|
||||
py10: 1,
|
||||
})
|
||||
|
||||
const textarea = (
|
||||
<Textarea
|
||||
id='chat-message-compose-input'
|
||||
inputRef={this.setTextbox}
|
||||
className={textareaClasses}
|
||||
disabled={disabled}
|
||||
placeholder='Type a new message...'
|
||||
autoFocus={false}
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
aria-autocomplete='list'
|
||||
/>
|
||||
)
|
||||
|
||||
const button = (
|
||||
<Button
|
||||
buttonRef={this.setSendBtn}
|
||||
disabled={disabled}
|
||||
onClick={this.handleOnSendChatMessage}
|
||||
>
|
||||
<Text color='inherit' className={_s.px10}>Send</Text>
|
||||
</Button>
|
||||
)
|
||||
|
||||
if (isXS) {
|
||||
return (
|
||||
<div className={[_s.d, _s.z4, _s.minH58PX, _s.w100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.minH58PX, _s.bgPrimary, _s.aiCenter, _s.z3, _s.bottom0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
|
||||
<div className={[_s.d, _s.w100PC, _s.pb5, _s.px15, _s.aiCenter, _s.jcCenter, _s.saveAreaInsetPB, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.w100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.minH58PX, _s.w100PC, _s.borderTop1PX, _s.borderColorSecondary, _s.px10].join(' ')}>
|
||||
<div className={[_s.d, _s.pr15, _s.flexGrow1, _s.py10].join(' ')}>
|
||||
{textarea}
|
||||
</div>
|
||||
<div className={[_s.d, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
|
||||
{button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.posAbs, _s.bottom0, _s.left0, _s.right0, _s.flexRow, _s.aiCenter, _s.minH58PX, _s.bgPrimary, _s.w100PC, _s.borderTop1PX, _s.borderColorSecondary, _s.px15].join(' ')}>
|
||||
<div className={[_s.d, _s.pr15, _s.flexGrow1, _s.py10].join(' ')}>
|
||||
<Textarea
|
||||
id='chat-message-compose-input'
|
||||
inputRef={this.setTextbox}
|
||||
className={textareaClasses}
|
||||
disabled={disabled}
|
||||
placeholder='Type a new message...'
|
||||
autoFocus={false}
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
aria-autocomplete='list'
|
||||
/>
|
||||
{textarea}
|
||||
</div>
|
||||
<div className={[_s.d, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={this.handleOnSendChatMessage}
|
||||
>
|
||||
<Text color='inherit' className={_s.px10}>Send</Text>
|
||||
</Button>
|
||||
{button}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -135,6 +159,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
ChatMessagesComposeForm.propTypes = {
|
||||
chatConversationId: PropTypes.string,
|
||||
isXS: PropTypes.bool,
|
||||
onSendMessage: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class ChatMessageItem extends ImmutablePureComponent {
|
||||
const account = chatMessage.get('account')
|
||||
if (!account) return <div />
|
||||
|
||||
const content = { __html: chatMessage.get('text') }
|
||||
const content = { __html: chatMessage.get('text_html') }
|
||||
const alt = account.get('id', null) === me
|
||||
const createdAt = chatMessage.get('created_at')
|
||||
|
||||
|
||||
@@ -2,20 +2,25 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import moment from 'moment-mini'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { List as ImmutableList } from 'immutable'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { createSelector } from 'reselect'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { me } from '../../../initial_state'
|
||||
import { CX, MOUSE_IDLE_DELAY } from '../../../constants'
|
||||
import { setChatConversationSelected } from '../../../actions/chats'
|
||||
import {
|
||||
expandChatMessages,
|
||||
scrollBottomChatMessageConversation,
|
||||
} from '../../../actions/chat_conversation_messages'
|
||||
import ScrollableList from '../../../components/scrollable_list'
|
||||
import IntersectionObserverArticle from '../../../components/intersection_observer_article'
|
||||
import IntersectionObserverWrapper from '../../ui/util/intersection_observer_wrapper'
|
||||
import ChatMessagePlaceholder from '../../../components/placeholder/chat_message_placeholder'
|
||||
import ChatMessageItem from './chat_message_item'
|
||||
import ColumnIndicator from '../../../components/column_indicator'
|
||||
import LoadMore from '../../../components/load_more'
|
||||
|
||||
class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
|
||||
@@ -23,6 +28,13 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
isRefreshing: false,
|
||||
}
|
||||
|
||||
intersectionObserverWrapper = new IntersectionObserverWrapper()
|
||||
|
||||
mouseIdleTimer = null
|
||||
mouseMovedRecently = false
|
||||
lastScrollWasSynthetic = false
|
||||
scrollToTopOnMouseIdle = false
|
||||
|
||||
componentDidMount () {
|
||||
const { chatConversationId } = this.props
|
||||
this.props.onExpandChatMessages(chatConversationId)
|
||||
@@ -30,6 +42,8 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onSetChatConversationSelected(null)
|
||||
this.detachScrollListener()
|
||||
this.detachIntersectionObserver()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@@ -40,20 +54,48 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = (sinceId) => {
|
||||
const { chatConversationId, dispatch } = this.props
|
||||
this.props.onExpandChatMessages(chatConversationId, { sinceId })
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
// Reset the scroll position when a new child comes in in order not to
|
||||
// jerk the scrollbar around if you're already scrolled down the page.
|
||||
if (snapshot !== null && this.scrollContainerRef) {
|
||||
console.log("snapshot:", snapshot)
|
||||
this.setScrollTop(this.scrollContainerRef.scrollHeight - snapshot)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.isRefreshing) {
|
||||
this.setState({ isRefreshing: false })
|
||||
}
|
||||
if (prevProps.chatMessageIds.size === 0 && this.props.chatMessageIds.size > 0) {
|
||||
this.containerNode.scrollTop = this.containerNode.scrollHeight
|
||||
|
||||
if (prevProps.chatMessageIds.size === 0 && this.props.chatMessageIds.size > 0 && this.scrollContainerRef) {
|
||||
this.scrollContainerRef.scrollTop = this.scrollContainerRef.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
attachScrollListener() {
|
||||
if (!this.scrollContainerRef) return
|
||||
this.scrollContainerRef.addEventListener('scroll', this.handleScroll)
|
||||
this.scrollContainerRef.addEventListener('wheel', this.handleWheel)
|
||||
}
|
||||
|
||||
detachScrollListener() {
|
||||
if (!this.scrollContainerRef) return
|
||||
this.scrollContainerRef.removeEventListener('scroll', this.handleScroll)
|
||||
this.scrollContainerRef.removeEventListener('wheel', this.handleWheel)
|
||||
}
|
||||
|
||||
attachIntersectionObserver() {
|
||||
this.intersectionObserverWrapper.connect()
|
||||
}
|
||||
|
||||
detachIntersectionObserver() {
|
||||
this.intersectionObserverWrapper.disconnect()
|
||||
}
|
||||
|
||||
onLoadMore = (maxId) => {
|
||||
const { chatConversationId } = this.props
|
||||
this.props.onExpandChatMessages(chatConversationId, { maxId })
|
||||
}
|
||||
|
||||
getCurrentChatMessageIndex = (id) => {
|
||||
// : todo :
|
||||
return this.props.chatMessageIds.indexOf(id)
|
||||
@@ -69,14 +111,13 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
this._selectChild(elementIndex, false)
|
||||
}
|
||||
|
||||
handleLoadOlder = debounce(() => {
|
||||
this.handleLoadMore(this.props.chatMessageIds.size > 0 ? this.props.chatMessageIds.last() : undefined)
|
||||
}, 300, { leading: true })
|
||||
|
||||
handleOnReload = debounce(() => {
|
||||
this.handleLoadMore()
|
||||
this.setState({ isRefreshing: true })
|
||||
}, 300, { trailing: true })
|
||||
setScrollTop = (newScrollTop) => {
|
||||
if (!this.scrollContainerRef) return
|
||||
if (this.scrollContainerRef.scrollTop !== newScrollTop) {
|
||||
this.lastScrollWasSynthetic = true
|
||||
this.scrollContainerRef.scrollTop = newScrollTop
|
||||
}
|
||||
}
|
||||
|
||||
_selectChild(index, align_top) {
|
||||
const container = this.node.node
|
||||
@@ -92,6 +133,83 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadOlder = debounce(() => {
|
||||
const maxId = this.props.chatMessageIds.size > 0 ? this.props.chatMessageIds.last() : undefined
|
||||
this.onLoadMore(maxId)
|
||||
}, 300, { leading: true })
|
||||
|
||||
handleScroll = throttle(() => {
|
||||
if (this.scrollContainerRef) {
|
||||
const { offsetHeight, scrollTop, scrollHeight } = this.scrollContainerRef
|
||||
const offset = scrollHeight - scrollTop - offsetHeight
|
||||
|
||||
if (scrollTop < 100 && this.props.hasMore && !this.props.isLoading) {
|
||||
this.handleLoadOlder()
|
||||
}
|
||||
|
||||
if (offset < 100) {
|
||||
this.props.onScrollToBottom()
|
||||
}
|
||||
|
||||
if (!this.lastScrollWasSynthetic) {
|
||||
// If the last scroll wasn't caused by setScrollTop(), assume it was
|
||||
// intentional and cancel any pending scroll reset on mouse idle
|
||||
this.scrollToTopOnMouseIdle = false
|
||||
}
|
||||
this.lastScrollWasSynthetic = false
|
||||
}
|
||||
}, 150, {
|
||||
trailing: true,
|
||||
})
|
||||
|
||||
handleWheel = throttle(() => {
|
||||
this.scrollToTopOnMouseIdle = false
|
||||
}, 150, {
|
||||
trailing: true,
|
||||
})
|
||||
|
||||
clearMouseIdleTimer = () => {
|
||||
if (this.mouseIdleTimer === null) return
|
||||
|
||||
clearTimeout(this.mouseIdleTimer)
|
||||
this.mouseIdleTimer = null
|
||||
}
|
||||
|
||||
handleMouseMove = throttle(() => {
|
||||
// As long as the mouse keeps moving, clear and restart the idle timer.
|
||||
this.clearMouseIdleTimer()
|
||||
this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY)
|
||||
|
||||
// Only set if we just started moving and are scrolled to the top.
|
||||
if (!this.mouseMovedRecently && this.scrollContainerRef.scrollTop === 0) {
|
||||
this.scrollToTopOnMouseIdle = true
|
||||
}
|
||||
|
||||
// Save setting this flag for last, so we can do the comparison above.
|
||||
this.mouseMovedRecently = true
|
||||
}, MOUSE_IDLE_DELAY / 2)
|
||||
|
||||
handleMouseIdle = () => {
|
||||
if (this.scrollToTopOnMouseIdle) {
|
||||
this.setScrollTop(0)
|
||||
}
|
||||
|
||||
this.mouseMovedRecently = false
|
||||
this.scrollToTopOnMouseIdle = false
|
||||
}
|
||||
|
||||
getSnapshotBeforeUpdate(prevProps) {
|
||||
const someItemInserted = prevProps.chatMessageIds.size > 0 &&
|
||||
prevProps.chatMessageIds.size < this.props.chatMessageIds.size &&
|
||||
prevProps.chatMessageIds.get(prevProps.chatMessageIds.size - 1) !== this.props.chatMessageIds.get(this.props.chatMessageIds.size - 1)
|
||||
|
||||
if (someItemInserted && (this.scrollContainerRef.scrollTop > 0 || this.mouseMovedRecently)) {
|
||||
return this.scrollContainerRef.scrollHeight - this.scrollContainerRef.scrollTop
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c
|
||||
}
|
||||
@@ -100,6 +218,15 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
this.containerNode = c
|
||||
}
|
||||
|
||||
setScrollContainerRef = (c) => {
|
||||
this.scrollContainerRef = c
|
||||
|
||||
this.attachScrollListener()
|
||||
this.attachIntersectionObserver()
|
||||
// Handle initial scroll posiiton
|
||||
this.handleScroll()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
chatConversationId,
|
||||
@@ -109,16 +236,13 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
hasMore,
|
||||
onScrollToBottom,
|
||||
onScroll,
|
||||
isXS,
|
||||
} = this.props
|
||||
const { isRefreshing } = this.state
|
||||
|
||||
if (isPartial || (isLoading && chatMessageIds.size === 0)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let scrollableContent = []
|
||||
let emptyContent = []
|
||||
|
||||
|
||||
if (isLoading || chatMessageIds.size > 0) {
|
||||
for (let i = 0; i < chatMessageIds.count(); i++) {
|
||||
const chatMessageId = chatMessageIds.get(i)
|
||||
@@ -128,8 +252,8 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
<div
|
||||
key={`chat-message-gap:${(i + 1)}`}
|
||||
disabled={isLoading}
|
||||
sinceId={i > 0 ? chatMessageIds.get(i - 1) : null}
|
||||
onClick={this.handleLoadMore}
|
||||
maxId={i > 0 ? chatMessageIds.get(i - 1) : null}
|
||||
onClick={this.handleLoadOlder}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -148,23 +272,67 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[_s.d, _s.boxShadowNone, _s.posAbs, _s.bottom60PX, _s.left0, _s.right0, _s.px15, _s.py15, _s.top60PX, _s.w100PC, _s.overflowYScroll].join(' ')}
|
||||
ref={this.containerRef}
|
||||
>
|
||||
<ScrollableList
|
||||
scrollRef={this.setRef}
|
||||
onLoadMore={this.handleLoadMore && this.handleLoadOlder}
|
||||
scrollKey='chat_messages'
|
||||
hasMore={hasMore}
|
||||
emptyMessage='No chats found'
|
||||
onScrollToBottom={onScrollToBottom}
|
||||
onScroll={onScroll}
|
||||
isLoading={isLoading}
|
||||
const childrenCount = React.Children.count(scrollableContent)
|
||||
if (isLoading || childrenCount > 0 || hasMore) {
|
||||
const containerClasses = CX({
|
||||
d: 1,
|
||||
bgPrimary: 1,
|
||||
boxShadowNone: 1,
|
||||
posAbs: !isXS,
|
||||
bottom60PX: !isXS,
|
||||
left0: !isXS,
|
||||
right0: !isXS,
|
||||
top60PX: !isXS,
|
||||
w100PC: 1,
|
||||
overflowHidden: 1,
|
||||
})
|
||||
return (
|
||||
<div
|
||||
onMouseMove={this.handleMouseMove}
|
||||
className={containerClasses}
|
||||
ref={this.containerRef}
|
||||
>
|
||||
{scrollableContent}
|
||||
</ScrollableList>
|
||||
<div
|
||||
className={[_s.d, _s.h100PC, _s.w100PC, _s.px15, _s.py15, _s.overflowYScroll].join(' ')}
|
||||
ref={this.setScrollContainerRef}
|
||||
>
|
||||
{
|
||||
(hasMore && !isLoading) &&
|
||||
<LoadMore onClick={this.handleLoadOlder} />
|
||||
}
|
||||
|
||||
{
|
||||
isLoading &&
|
||||
<ColumnIndicator type='loading' />
|
||||
}
|
||||
|
||||
<div role='feed'>
|
||||
{
|
||||
!!scrollableContent &&
|
||||
scrollableContent.map((child, index) => (
|
||||
<IntersectionObserverArticle
|
||||
key={`chat_message:${chatConversationId}:${index}`}
|
||||
id={`chat_message:${chatConversationId}:${index}`}
|
||||
index={index}
|
||||
listLength={childrenCount}
|
||||
intersectionObserverWrapper={this.intersectionObserverWrapper}
|
||||
saveHeightKey={`chat_messages:${chatConversationId}:${index}`}
|
||||
>
|
||||
{child}
|
||||
</IntersectionObserverArticle>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.boxShadowNone, _s.posAbs, _s.bottom60PX, _s.left0, _s.right0, _s.top60PX, _s.w100PC, _s.overflowHidden].join(' ')}>
|
||||
<div className={[_s.d, _s.h100PC, _s.w100PC, _s.px15, _s.py15, _s.overflowYScroll].join(' ')}>
|
||||
<ColumnIndicator type='error' message='No chat messages found' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -207,6 +375,7 @@ ChatMessageScrollingList.propTypes = {
|
||||
onClearTimeline: PropTypes.func.isRequired,
|
||||
onScrollToTop: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired,
|
||||
isXS: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessageScrollingList)
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
|
||||
import ChatSettingsHeader from './chat_settings_header'
|
||||
import List from '../../../components/list'
|
||||
|
||||
class ChatSettingsSidebar extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ResponsiveClassesComponent
|
||||
classNames={[_s.d, _s.w340PX, _s.h100PC, _s.bgPrimary, _s.borderLeft1PX, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}
|
||||
classNamesSmall={[_s.d, _s.w300PX, _s.h100PC, _s.bgPrimary, _s.borderLeft1PX, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}
|
||||
>
|
||||
<ChatSettingsHeader />
|
||||
<List
|
||||
items={[
|
||||
{
|
||||
title: 'Preferences',
|
||||
to: '/messages/settings',
|
||||
},
|
||||
{
|
||||
title: 'Message Requests',
|
||||
to: '/messages/requests',
|
||||
},
|
||||
{
|
||||
title: 'Blocked Chats',
|
||||
to: '/messages/blocks',
|
||||
},
|
||||
{
|
||||
title: 'Muted Chats',
|
||||
to: '/messages/mutes',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ResponsiveClassesComponent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChatSettingsSidebar
|
||||
@@ -10,7 +10,7 @@ class Messages extends React.PureComponent {
|
||||
|
||||
render () {
|
||||
const {
|
||||
account,
|
||||
isXS,
|
||||
selectedChatConversationId,
|
||||
chatConverationIsRequest,
|
||||
} = this.props
|
||||
@@ -48,6 +48,7 @@ const mapStateToProps = (state, props) => {
|
||||
}
|
||||
|
||||
Messages.propTypes = {
|
||||
isXS: PropTypes.bool,
|
||||
selectedChatConversationId: PropTypes.string,
|
||||
chatConverationIsRequest: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ class SwitchingArea extends React.PureComponent {
|
||||
<WrappedRoute path='/news/view/:trendsRSSId' page={NewsPage} component={NewsView} content={children} componentParams={{ title: 'News RSS Feed' }} />
|
||||
|
||||
<WrappedRoute path='/messages' exact page={MessagesPage} component={Messages} content={children} componentParams={{ source: 'approved' }} />
|
||||
<WrappedRoute path='/messages/new' exact page={BasicPage} component={ChatConversationCreate} content={children} componentParams={{ title: 'New Message' }} />
|
||||
<WrappedRoute path='/messages/settings' exact page={MessagesPage} component={MessagesSettings} content={children} componentParams={{ isSettings: true }} />
|
||||
<WrappedRoute path='/messages/requests' exact page={MessagesPage} component={ChatConversationRequests} content={children} componentParams={{ isSettings: true, source: 'requested' }} />
|
||||
<WrappedRoute path='/messages/blocks' exact page={MessagesPage} component={ChatConversationBlockedAccounts} content={children} componentParams={{ isSettings: true }} />
|
||||
|
||||
@@ -23,9 +23,9 @@ export default class IntersectionObserverWrapper {
|
||||
};
|
||||
|
||||
this.observer = new IntersectionObserver(onIntersection, options);
|
||||
this.observerBacklog.forEach(([ id, node, callback ]) => {
|
||||
Array.isArray(this.observerBacklog) ? this.observerBacklog.forEach(([ id, node, callback ]) => {
|
||||
this.observe(id, node, callback);
|
||||
});
|
||||
}) : null;
|
||||
this.observerBacklog = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user