Progress on dms, code cleanup
Progress on dms, code cleanup
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { openModal } from '../../../actions/modal'
|
||||
import { MODAL_CHAT_CONVERSATION_CREATE } from '../../../constants'
|
||||
import Text from '../../../components/text'
|
||||
import Button from '../../../components/button'
|
||||
|
||||
class ChatEmptyMessageBlock extends React.PureComponent {
|
||||
|
||||
handleOnCreateNewChat = () => {
|
||||
this.props.onOpenChatConversationCreateModal()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={[_s.d, _s.bgPrimary, _s.h100PC, _s.w100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.w100PC, _s.h100PC, _s.aiCenter, _s.jcCenter].join(' ')}>
|
||||
<Text weight='bold' size='extraLarge'>
|
||||
You don’t have a message selected
|
||||
</Text>
|
||||
<Text size='medium' color='secondary' className={_s.py10}>
|
||||
Choose one from your existing messages, or start a new one.
|
||||
</Text>
|
||||
<Button className={_s.mt10} onClick={this.handleOnCreateNewChat}>
|
||||
<Text color='inherit' weight='bold' className={_s.px15}>
|
||||
New Message
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenChatConversationCreateModal() {
|
||||
dispatch(openModal(MODAL_CHAT_CONVERSATION_CREATE))
|
||||
}
|
||||
})
|
||||
|
||||
ChatEmptyMessageBlock.propTypes = {
|
||||
onOpenChatConversationCreateModal: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ChatEmptyMessageBlock)
|
||||
@@ -0,0 +1,98 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import debounce from 'lodash.debounce'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import {
|
||||
fetchChatConversations,
|
||||
expandChatConversations,
|
||||
fetchChatConversationRequested,
|
||||
expandChatConversationRequested,
|
||||
} from '../../../actions/chat_conversations'
|
||||
import AccountPlaceholder from '../../../components/placeholder/account_placeholder'
|
||||
import ChatConversationsListItem from './chat_conversations_list_item'
|
||||
import ScrollableList from '../../../components/scrollable_list'
|
||||
|
||||
class ChatConversationsList extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
console.log("componentDidMount:", this.props.source)
|
||||
this.props.onFetchChatConversations(this.props.source)
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.onExpandChatConversations(this.props.source)
|
||||
}, 300, { leading: true })
|
||||
|
||||
render() {
|
||||
const {
|
||||
hasMore,
|
||||
isLoading,
|
||||
source,
|
||||
chatConversationIds,
|
||||
} = this.props
|
||||
|
||||
console.log("---source:", source, chatConversationIds)
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC, _s.overflowHidden, _s.boxShadowNone].join(' ')}>
|
||||
<ScrollableList
|
||||
scrollKey='chat-conversations'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading}
|
||||
placeholderComponent={AccountPlaceholder}
|
||||
placeholderCount={3}
|
||||
emptyMessage='Empty'
|
||||
>
|
||||
{
|
||||
!!chatConversationIds && chatConversationIds.map((chatConversationId, i) => (
|
||||
<ChatConversationsListItem
|
||||
key={`chat-conversation-${i}`}
|
||||
chatConversationId={chatConversationId}
|
||||
source={source}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ScrollableList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { source }) => ({
|
||||
chatConversationIds: state.getIn(['chat_conversation_lists', source, 'items']),
|
||||
hasMore: !!state.getIn(['chat_conversation_lists', source, 'next']),
|
||||
isLoading: state.getIn(['chat_conversation_lists', source, 'isLoading']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchChatConversations(source) {
|
||||
if (source ==='approved') {
|
||||
dispatch(fetchChatConversations())
|
||||
} else if (source ==='requested') {
|
||||
dispatch(fetchChatConversationRequested())
|
||||
}
|
||||
},
|
||||
onExpandChatConversations(source) {
|
||||
if (source ==='approved') {
|
||||
dispatch(expandChatConversations())
|
||||
} else if (source ==='requested') {
|
||||
dispatch(expandChatConversationRequested())
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
ChatConversationsList.propTypes = {
|
||||
chatConversationIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
onFetchChatConversations: PropTypes.func.isRequired,
|
||||
onExpandChatConversations: PropTypes.func.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationsList)
|
||||
@@ -0,0 +1,116 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { makeGetChatConversation } from '../../../selectors'
|
||||
import { setChatConversationSelected } from '../../../actions/chats'
|
||||
import { CX } from '../../../constants'
|
||||
import Input from '../../../components/input'
|
||||
import DisplayNameGroup from '../../../components/display_name_group'
|
||||
import DisplayName from '../../../components/display_name'
|
||||
import AvatarGroup from '../../../components/avatar_group'
|
||||
import Text from '../../../components/text'
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp'
|
||||
|
||||
class ChatConversationsListItem extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
handleOnClick = () => {
|
||||
const { chatConversationId } = this.props
|
||||
this.props.onSetChatConversationSelected(chatConversationId)
|
||||
this.context.router.history.push(`/messages/${chatConversationId}`)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
selected,
|
||||
selectedId,
|
||||
chatConversation,
|
||||
chatConversationId,
|
||||
} = this.props
|
||||
|
||||
if (!chatConversation) return <div/>
|
||||
|
||||
const containerClasses = CX({
|
||||
d: 1,
|
||||
w100PC: 1,
|
||||
bgTransparent: 1,
|
||||
bgSubtle_onHover: 1,
|
||||
borderBottom1PX: 1,
|
||||
borderColorSecondary: 1,
|
||||
noUnderline: 1,
|
||||
outlineNone: 1,
|
||||
cursorPointer: 1,
|
||||
})
|
||||
|
||||
const innerContainerClasses = CX({
|
||||
d: 1,
|
||||
flexRow: 1,
|
||||
aiStart: 1,
|
||||
aiCenter: 0,
|
||||
px15: 1,
|
||||
py15: 1,
|
||||
borderRight4PX: selected,
|
||||
borderColorBrand: selected,
|
||||
})
|
||||
|
||||
const avatarSize = 46
|
||||
const otherAccounts = chatConversation.get('other_accounts')
|
||||
const lastMessage = chatConversation.get('last_chat_message', null)
|
||||
const content = { __html: !!lastMessage ? lastMessage.get('text', '') : '' }
|
||||
const date = !!lastMessage ? lastMessage.get('created_at') : chatConversation.get('created_at')
|
||||
|
||||
return (
|
||||
<button
|
||||
className={containerClasses}
|
||||
onClick={this.handleOnClick}
|
||||
>
|
||||
<div className={innerContainerClasses}>
|
||||
<AvatarGroup accounts={otherAccounts} size={avatarSize} noHover />
|
||||
|
||||
<div className={[_s.d, _s.pl10, _s.overflowHidden, _s.flexNormal].join(' ')}>
|
||||
<div className={[_s.d, _s.flexRow, _s.aiCenter].join(' ')}>
|
||||
<div className={[_s.d, _s.pt2, _s.pr5, _s.noUnderline, _s.overflowHidden, _s.flexNormal, _s.flexRow, _s.aiStart, _s.aiCenter].join(' ')}>
|
||||
<div className={_s.maxW100PC42PX}>
|
||||
<DisplayName account={otherAccounts.get(0)} noHover />
|
||||
</div>
|
||||
<Text size='extraSmall' color='secondary' className={_s.mlAuto}>
|
||||
<RelativeTimestamp timestamp={date} />
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={[_s.py5, _s.dangerousContent, _s.textAlignLeft].join(' ')} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { chatConversationId }) => ({
|
||||
chatConversation: makeGetChatConversation()(state, { id: chatConversationId }),
|
||||
selectedId: state.getIn(['chats', 'selectedChatConversationId'], null),
|
||||
selected: state.getIn(['chats', 'selectedChatConversationId'], null) === chatConversationId,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onSetChatConversationSelected: (chatConversationId) => {
|
||||
dispatch(setChatConversationSelected(chatConversationId))
|
||||
},
|
||||
})
|
||||
|
||||
ChatConversationsListItem.propTypes = {
|
||||
chatConversationId: PropTypes.string.isRequired,
|
||||
chatConversation: ImmutablePropTypes.map,
|
||||
onSetChatConversationSelected: PropTypes.func.isRequired,
|
||||
selected: PropTypes.bool.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationsListItem)
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { shortNumberFormat } from '../../../utils/numbers'
|
||||
import { fetchChatConversationRequestedCount } from '../../../actions/chat_conversations'
|
||||
import Text from '../../../components/text'
|
||||
import Icon from '../../../components/icon'
|
||||
|
||||
class ChatConversationRequestsListItem extends React.PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.onFetchChatConversationRequestedCount()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { requestCount } = this.props
|
||||
|
||||
if (!requestCount || requestCount < 1) return null
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
className={[_s.d, _s.w100PC, _s.bgTransparent, _s.bgSubtle_onHover, _s.borderBottom1PX, _s.borderColorSecondary, _s.noUnderline, _s.outlineNone, _s.cursorPointer].join(' ')}
|
||||
to='/messages/requests'
|
||||
>
|
||||
<div className={[_s.d, _s.px15, _s.py15, _s.aiCenter, _s.flexRow].join(' ')}>
|
||||
<Text size='medium'>Message Requests</Text>
|
||||
<Text size='medium' className={[_s.mlAuto, _s.mr15].join(' ')}>
|
||||
{shortNumberFormat(requestCount)}
|
||||
</Text>
|
||||
<Icon id='angle-right' size='10px' className={_s.cPrimary} />
|
||||
</div>
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
requestCount: state.getIn(['chats', 'chatConversationRequestCount'], 0),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchChatConversationRequestedCount: () => dispatch(fetchChatConversationRequestedCount()),
|
||||
})
|
||||
|
||||
ChatConversationRequestsListItem.propTypes = {
|
||||
requestCount: PropTypes.number,
|
||||
onFetchChatConversationRequestedCount: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
ChatConversationRequestsListItem.defaultProps = {
|
||||
requestCount: 0,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationRequestsListItem)
|
||||
@@ -1,11 +1,8 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import Input from '../../../components/input'
|
||||
|
||||
class MessagesSearch extends ImmutablePureComponent {
|
||||
class ChatConversationsSearch extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
@@ -17,7 +14,7 @@ class MessagesSearch extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
children
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
@@ -34,12 +31,8 @@ class MessagesSearch extends ImmutablePureComponent {
|
||||
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: "What's on your mind?" },
|
||||
})
|
||||
|
||||
MessagesSearch.propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
ChatConversationsSearch.propTypes = {
|
||||
//
|
||||
}
|
||||
|
||||
export default injectIntl(MessagesSearch)
|
||||
export default ChatConversationsSearch
|
||||
@@ -0,0 +1,140 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { connect } from 'react-redux'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { openModal } from '../../../actions/modal'
|
||||
import { sendChatMessage } from '../../../actions/chat_messages'
|
||||
import { CX } from '../../../constants'
|
||||
import Button from '../../../components/button'
|
||||
import Input from '../../../components/input'
|
||||
|
||||
class ChatMessagesComposeForm extends React.PureComponent {
|
||||
|
||||
state = {
|
||||
focused: false,
|
||||
value: '',
|
||||
}
|
||||
|
||||
handleOnSendChatMessage = () => {
|
||||
this.props.onSendChatMessage(this.state.value, this.props.chatConversationId)
|
||||
this.setState({ value: '' })
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
this.setState({ value: e.target.value })
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
this.setState({ focused: false });
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
this.setState({ focused: true });
|
||||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
const { disabled } = this.props;
|
||||
|
||||
if (disabled) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
document.querySelector('#gabsocial').focus()
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
//
|
||||
break;
|
||||
}
|
||||
|
||||
// if (e.defaultPrevented || !this.props.onKeyDown) return;
|
||||
}
|
||||
|
||||
setTextbox = (c) => {
|
||||
this.textbox = c
|
||||
}
|
||||
|
||||
render () {
|
||||
const { 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,
|
||||
wrap: 1,
|
||||
resizeNone: 1,
|
||||
bgTransparent: 1,
|
||||
outlineNone: 1,
|
||||
lineHeight125: 1,
|
||||
cPrimary: 1,
|
||||
px10: 1,
|
||||
fs14PX: 1,
|
||||
maxH200PX: 1,
|
||||
borderColorSecondary: 1,
|
||||
border1PX: 1,
|
||||
radiusSmall: 1,
|
||||
py10: 1,
|
||||
})
|
||||
|
||||
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, _s.py5].join(' ')}>
|
||||
<div className={[_s.d, _s.pr15, _s.flexGrow1].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'
|
||||
/>
|
||||
</div>
|
||||
<div className={[_s.d, _s.h100PC, _s.mtAuto, _s.pb5].join(' ')}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={this.handleOnSendChatMessage}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onSendChatMessage(text, chatConversationId) {
|
||||
dispatch(sendChatMessage(text, chatConversationId))
|
||||
},
|
||||
})
|
||||
|
||||
ChatMessagesComposeForm.propTypes = {
|
||||
chatConversationId: PropTypes.string,
|
||||
onSendMessage: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ChatMessagesComposeForm)
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { connect } from 'react-redux'
|
||||
import { makeGetChatConversation } from '../../../selectors'
|
||||
import { openModal } from '../../../actions/modal'
|
||||
import { approveChatConversationRequest } from '../../../actions/chat_conversations'
|
||||
import { MODAL_CHAT_CONVERSATION_CREATE } from '../../../constants'
|
||||
import Button from '../../../components/button'
|
||||
import AvatarGroup from '../../../components/avatar_group'
|
||||
import DisplayName from '../../../components/display_name'
|
||||
import Text from '../../../components/text'
|
||||
|
||||
class ChatMessageHeader extends React.PureComponent {
|
||||
|
||||
handleOnApproveMessageRequest = () => {
|
||||
this.props.onApproveChatConversationRequest(this.props.chatConversationId)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { chatConversation } = this.props
|
||||
|
||||
const isChatConversationRequest = !!chatConversation ? !chatConversation.get('is_approved') : false
|
||||
const otherAccounts = !!chatConversation ? chatConversation.get('other_accounts') : null
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.posAbs, _s.top0, _s.left0, _s.right0, _s.flexRow, _s.aiCenter, _s.h60PX, _s.w100PC, _s.borderBottom1PX, _s.borderColorSecondary, _s.bgPrimary, _s.px15, _s.py5].join(' ')}>
|
||||
|
||||
{
|
||||
!!otherAccounts &&
|
||||
<React.Fragment>
|
||||
<AvatarGroup accounts={otherAccounts} size={34} noHover />
|
||||
<div className={[_s.d, _s.pl10, _s.maxW100PC86PX, _s.overflowHidden].join(' ')}>
|
||||
<DisplayName account={otherAccounts.get(0)} isMultiline />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
<Button
|
||||
isNarrow
|
||||
onClick={undefined}
|
||||
color='primary'
|
||||
backgroundColor='secondary'
|
||||
className={[_s.mlAuto, _s.px5].join(' ')}
|
||||
icon='ellipsis'
|
||||
iconSize='18px'
|
||||
/>
|
||||
{
|
||||
isChatConversationRequest &&
|
||||
<Button
|
||||
isNarrow
|
||||
onClick={this.handleOnApproveMessageRequest}
|
||||
className={_s.ml10}
|
||||
>
|
||||
<Text>
|
||||
Approve Message Request
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { chatConversationId }) => ({
|
||||
chatConversation: makeGetChatConversation()(state, { id: chatConversationId }),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenChatConversationCreateModal() {
|
||||
dispatch(openModal(MODAL_CHAT_CONVERSATION_CREATE))
|
||||
},
|
||||
onApproveChatConversationRequest(chatConversationId) {
|
||||
dispatch(approveChatConversationRequest(chatConversationId))
|
||||
}
|
||||
})
|
||||
|
||||
ChatMessageHeader.propTypes = {
|
||||
onOpenChatConversationCreateModal: PropTypes.func.isRequired,
|
||||
chatConversationId: PropTypes.string,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessageHeader)
|
||||
@@ -0,0 +1,179 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import moment from 'moment-mini'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { CX } from '../../../constants'
|
||||
import { me } from '../../../initial_state'
|
||||
import Input from '../../../components/input'
|
||||
import Avatar from '../../../components/avatar'
|
||||
import Button from '../../../components/button'
|
||||
import Text from '../../../components/text'
|
||||
import { makeGetChatMessage } from '../../../selectors'
|
||||
|
||||
class ChatMessageItem extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
isNewDay: false,
|
||||
isCloseToMyLast: false,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { lastChatMessageSameSender, lastChatMessageDate } = this.props
|
||||
if (lastChatMessageDate) {
|
||||
const createdAt = this.props.chatMessage.get('created_at')
|
||||
const isNewDay = moment(createdAt).format('L') !== moment(lastChatMessageDate).format('L')
|
||||
const isCloseToMyLast = moment(lastChatMessageDate).diff(createdAt, 'minutes') < 60 && lastChatMessageSameSender && !isNewDay
|
||||
this.setState({
|
||||
isNewDay,
|
||||
isCloseToMyLast,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleOnMouseEnter = () => {
|
||||
this.setState({ isHovering: true })
|
||||
}
|
||||
|
||||
handleOnMouseLeave = () => {
|
||||
this.setState({ isHovering: false })
|
||||
}
|
||||
|
||||
handleMoreClick = () => {
|
||||
//
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
chatMessage,
|
||||
isHidden,
|
||||
lastChatMessageDate,
|
||||
} = this.props
|
||||
const {
|
||||
isCloseToMyLast,
|
||||
isHovering,
|
||||
isNewDay,
|
||||
} = this.state
|
||||
|
||||
if (!chatMessage) return <div />
|
||||
|
||||
const account = chatMessage.get('account')
|
||||
const content = { __html: chatMessage.get('text') }
|
||||
const alt = account.get('id', null) === me
|
||||
const createdAt = chatMessage.get('created_at')
|
||||
|
||||
if (isHidden) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{account.get('display_name')}
|
||||
<div dangerouslySetInnerHTML={content} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const messageContainerClasses = CX({
|
||||
d: 1,
|
||||
flexRow: !alt,
|
||||
flexRowReverse: alt,
|
||||
pb5: 1,
|
||||
})
|
||||
|
||||
const messageInnerContainerClasses = CX({
|
||||
d: 1,
|
||||
px15: 1,
|
||||
py5: 1,
|
||||
bgTertiary: !alt,
|
||||
bgSecondary: alt,
|
||||
circle: 1,
|
||||
ml10: 1,
|
||||
mr10: 1,
|
||||
})
|
||||
|
||||
const lowerContainerClasses = CX({
|
||||
d: 1,
|
||||
pt10: 1,
|
||||
posAbs: 1,
|
||||
bottom0: 1,
|
||||
right0: alt,
|
||||
left0: !alt,
|
||||
displayNone: !isHovering,
|
||||
pl50: !alt,
|
||||
pr50: alt,
|
||||
})
|
||||
|
||||
const buttonContainerClasses = CX({
|
||||
d: 1,
|
||||
flexRow: 1,
|
||||
displayNone: !isHovering && alt,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[_s.d, _s.w100PC, _s.pb10].join(' ')}
|
||||
onMouseEnter={this.handleOnMouseEnter}
|
||||
onMouseLeave={this.handleOnMouseLeave}
|
||||
>
|
||||
{
|
||||
!!lastChatMessageDate && isNewDay &&
|
||||
<Text color='secondary' size='small' align='center' className={[_s.d, _s.py10].join(' ')}>
|
||||
{moment(createdAt).format('lll')}
|
||||
</Text>
|
||||
}
|
||||
|
||||
<div className={[_s.d, _s.w100PC, _s.pb15].join(' ')}>
|
||||
|
||||
<div className={messageContainerClasses}>
|
||||
<Avatar account={chatMessage.get('account')} size={38} />
|
||||
<div className={messageInnerContainerClasses}>
|
||||
<div className={[_s.py5, _s.dangerousContent, _s.cPrimary].join(' ')} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
{
|
||||
alt &&
|
||||
<div className={buttonContainerClasses}>
|
||||
<Button
|
||||
onClick={this.handleMoreClick}
|
||||
color='tertiary'
|
||||
backgroundColor='none'
|
||||
icon='ellipsis'
|
||||
iconSize='18px'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className={lowerContainerClasses}>
|
||||
<Text size='extraSmall' color='tertiary' align={alt ? 'right' : 'left'}>
|
||||
{moment(createdAt).format('lll')}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { lastChatMessageId, chatMessageId }) => ({
|
||||
chatMessage: makeGetChatMessage()(state, { id: chatMessageId }),
|
||||
lastChatMessageDate: lastChatMessageId ? state.getIn(['chat_messages', `${lastChatMessageId}`, 'created_at'], null) : null,
|
||||
lastChatMessageSameSender: lastChatMessageId ? state.getIn(['chat_messages', `${lastChatMessageId}`, 'from_account_id'], null) === state.getIn(['chat_messages', `${chatMessageId}`, 'from_account_id'], null) : false,
|
||||
})
|
||||
|
||||
ChatMessageItem.propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
lastChatMessageId: PropTypes.string,
|
||||
lastChatMessageDate: PropTypes.string,
|
||||
lastChatMessageSameSender: PropTypes.string,
|
||||
chatMessageId: PropTypes.string.isRequired,
|
||||
chatMessage: ImmutablePropTypes.map,
|
||||
isHidden: PropTypes.bool,
|
||||
alt: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChatMessageItem)
|
||||
@@ -0,0 +1,212 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import moment from 'moment-mini'
|
||||
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 { setChatConversationSelected } from '../../../actions/chats'
|
||||
import {
|
||||
expandChatMessages,
|
||||
scrollBottomChatMessageConversation,
|
||||
} from '../../../actions/chat_conversation_messages'
|
||||
import ScrollableList from '../../../components/scrollable_list'
|
||||
import ChatMessagePlaceholder from '../../../components/placeholder/chat_message_placeholder'
|
||||
import ChatMessageItem from './chat_message_item'
|
||||
|
||||
class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
isRefreshing: false,
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { chatConversationId } = this.props
|
||||
this.props.onExpandChatMessages(chatConversationId)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onSetChatConversationSelected(null)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const { chatConversationId } = nextProps
|
||||
|
||||
if (chatConversationId !== this.props.chatConversationId) {
|
||||
this.props.onExpandChatMessages(chatConversationId)
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = (sinceId) => {
|
||||
const { chatConversationId, dispatch } = this.props
|
||||
this.props.onExpandChatMessages(chatConversationId, { sinceId })
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentChatMessageIndex = (id) => {
|
||||
// : todo :
|
||||
return this.props.chatMessageIds.indexOf(id)
|
||||
}
|
||||
|
||||
handleMoveUp = (id) => {
|
||||
const elementIndex = this.getCurrentChatMessageIndex(id) - 1
|
||||
this._selectChild(elementIndex, true)
|
||||
}
|
||||
|
||||
handleMoveDown = (id) => {
|
||||
const elementIndex = this.getCurrentChatMessageIndex(id) + 1
|
||||
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 })
|
||||
|
||||
_selectChild(index, align_top) {
|
||||
const container = this.node.node
|
||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`)
|
||||
|
||||
if (element) {
|
||||
if (align_top && container.scrollTop > element.offsetTop) {
|
||||
element.scrollIntoView(true)
|
||||
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
||||
element.scrollIntoView(false)
|
||||
}
|
||||
element.focus()
|
||||
}
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c
|
||||
}
|
||||
|
||||
containerRef = (c) => {
|
||||
this.containerNode = c
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
chatConversationId,
|
||||
chatMessageIds,
|
||||
isLoading,
|
||||
isPartial,
|
||||
hasMore,
|
||||
onScrollToBottom,
|
||||
onScroll,
|
||||
} = 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)
|
||||
const lastChatMessageId = i > 0 ? chatMessageIds.get(i - 1) : null
|
||||
if (!chatMessageId) {
|
||||
scrollableContent.unshift(
|
||||
<div
|
||||
key={`chat-message-gap:${(i + 1)}`}
|
||||
disabled={isLoading}
|
||||
sinceId={i > 0 ? chatMessageIds.get(i - 1) : null}
|
||||
onClick={this.handleLoadMore}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
scrollableContent.unshift(
|
||||
<ChatMessageItem
|
||||
key={`chat-message-${chatConversationId}-${i}`}
|
||||
chatMessageId={chatMessageId}
|
||||
lastChatMessageId={lastChatMessageId}
|
||||
onMoveUp={this.handleMoveUp}
|
||||
onMoveDown={this.handleMoveDown}
|
||||
commentsLimited
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
>
|
||||
{scrollableContent}
|
||||
</ScrollableList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { chatConversationId }) => {
|
||||
if (!chatConversationId) return {}
|
||||
|
||||
return {
|
||||
chatMessageIds: state.getIn(['chat_conversation_messages', chatConversationId, 'items'], ImmutableList()),
|
||||
isLoading: state.getIn(['chat_conversation_messages', chatConversationId, 'isLoading'], true),
|
||||
isPartial: state.getIn(['chat_conversation_messages', chatConversationId, 'isPartial'], false),
|
||||
hasMore: state.getIn(['chat_conversation_messages', chatConversationId, 'hasMore']),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
onScrollToBottom: debounce(() => {
|
||||
dispatch(scrollBottomChatMessageConversation(ownProps.chatConversationId, true))
|
||||
}, 100),
|
||||
onScroll: debounce(() => {
|
||||
dispatch(scrollBottomChatMessageConversation(ownProps.chatConversationId, false))
|
||||
}, 100),
|
||||
onExpandChatMessages(chatConversationId, params) {
|
||||
dispatch(expandChatMessages(chatConversationId, params))
|
||||
},
|
||||
onSetChatConversationSelected: (chatConversationId) => {
|
||||
dispatch(setChatConversationSelected(chatConversationId))
|
||||
},
|
||||
})
|
||||
|
||||
ChatMessageScrollingList.propTypes = {
|
||||
chatMessageIds: ImmutablePropTypes.list.isRequired,
|
||||
chatConversationId: PropTypes.string.isRequired,
|
||||
onExpandChatMessages: PropTypes.func.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
isPartial: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
onClearTimeline: PropTypes.func.isRequired,
|
||||
onScrollToTop: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessageScrollingList)
|
||||
@@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import Heading from '../../../components/heading'
|
||||
import Button from '../../../components/button'
|
||||
|
||||
class MessagesHeader extends ImmutablePureComponent {
|
||||
class ChatSettingsHeader extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const {
|
||||
@@ -16,27 +16,19 @@ class MessagesHeader extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC, _s.h60PX, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<div className={[_s.d, _s.flexRow, _s.pl15, _s.pr5, _s.py15].join(' ')}>
|
||||
<Button
|
||||
noClasses
|
||||
className={[_s.d, _s.noUnderline, _s.jcCenter, _s.mr5, _s.aiCenter, _s.bgTransparent, _s.cursorPointer, _s.outlineNone].join(' ')}
|
||||
to='/messages'
|
||||
color='primary'
|
||||
backgroundColor='none'
|
||||
icon='angle-left'
|
||||
iconSize='16px'
|
||||
iconClassName={[_s.mr5, _s.cPrimary].join(' ')}
|
||||
/>
|
||||
<Heading size='h1'>
|
||||
Messages
|
||||
Chat Settings
|
||||
</Heading>
|
||||
<div className={[_s.d, _s.bgTransparent, _s.flexRow, _s.aiCenter, _s.jcCenter, _s.mlAuto].join(' ')}>
|
||||
<Button
|
||||
isNarrow
|
||||
onClick={undefined}
|
||||
className={[_s.ml5, _s.px15].join(' ')}
|
||||
>
|
||||
New
|
||||
</Button>
|
||||
<Button
|
||||
isNarrow
|
||||
onClick={undefined}
|
||||
color='brand'
|
||||
backgroundColor='none'
|
||||
className={_s.ml5}
|
||||
icon='cog'
|
||||
iconSize='18px'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -44,4 +36,4 @@ class MessagesHeader extends ImmutablePureComponent {
|
||||
|
||||
}
|
||||
|
||||
export default MessagesHeader
|
||||
export default ChatSettingsHeader
|
||||
@@ -1,116 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { CX } from '../../../constants'
|
||||
import Input from '../../../components/input'
|
||||
import Avatar from '../../../components/avatar'
|
||||
import Button from '../../../components/button'
|
||||
import Text from '../../../components/text'
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp'
|
||||
import { makeGetAccount } from '../../../selectors'
|
||||
|
||||
class MessagesItem extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
}
|
||||
|
||||
handleOnMouseEnter = () => {
|
||||
this.setState({ isHovering: true })
|
||||
}
|
||||
|
||||
handleOnMouseLeave = () => {
|
||||
this.setState({ isHovering: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
account,
|
||||
intl,
|
||||
alt,
|
||||
} = this.props
|
||||
const { isHovering } = this.state
|
||||
|
||||
const content = { __html: 'REEEE i heard you have the sauce2?' }
|
||||
const messageContainerClasses = CX({
|
||||
d: 1,
|
||||
flexRow: !alt,
|
||||
flexRowReverse: alt,
|
||||
})
|
||||
const messageInnerContainerClasses = CX({
|
||||
d: 1,
|
||||
px15: 1,
|
||||
py5: 1,
|
||||
bgSecondary: !alt,
|
||||
bgBrandLight: alt,
|
||||
circle: 1,
|
||||
ml10: 1,
|
||||
mr10: 1,
|
||||
})
|
||||
|
||||
const lowerContainerClasses = CX({
|
||||
d: 1,
|
||||
pt10: 1,
|
||||
pl50: !alt,
|
||||
pr50: alt,
|
||||
})
|
||||
|
||||
const buttonContainerClasses = CX({
|
||||
d: 1,
|
||||
flexRow: 1,
|
||||
displayNone: !isHovering,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[_s.d, _s.w100PC, _s.pb10].join(' ')}
|
||||
onMouseEnter={this.handleOnMouseEnter}
|
||||
onMouseLeave={this.handleOnMouseLeave}
|
||||
>
|
||||
<div className={[_s.d, _s.w100PC, _s.pb15].join(' ')}>
|
||||
|
||||
<div className={messageContainerClasses}>
|
||||
<Avatar account={account} size={38} />
|
||||
<div className={messageInnerContainerClasses}>
|
||||
<div className={[_s.py5, _s.dangerousContent].join(' ')} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
<div className={buttonContainerClasses}>
|
||||
<Button
|
||||
onClick={undefined}
|
||||
color='tertiary'
|
||||
backgroundColor='none'
|
||||
icon='ellipsis'
|
||||
iconSize='18px'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={lowerContainerClasses}>
|
||||
<Text size='small' color='tertiary' align={alt ? 'right' : 'left'}>
|
||||
Apr 16, 2020, 8:20 AM
|
||||
{ /* <RelativeTimestamp timestamp={'2020-20-10'} /> */ }
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: makeGetAccount()(state, '1'),
|
||||
})
|
||||
|
||||
MessagesItem.propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
alt: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(MessagesItem)
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import MessagesListItem from './messages_list_item'
|
||||
import { makeGetAccount } from '../../../selectors'
|
||||
|
||||
class MessagesList extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const {
|
||||
account,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC].join(' ')}>
|
||||
<MessagesListItem />
|
||||
<MessagesListItem selected />
|
||||
<MessagesListItem />
|
||||
<MessagesListItem />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: makeGetAccount()(state, '1'),
|
||||
})
|
||||
|
||||
MessagesList.propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(MessagesList)
|
||||
@@ -1,107 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { CX } from '../../../constants'
|
||||
import Input from '../../../components/input'
|
||||
import DisplayName from '../../../components/display_name'
|
||||
import Avatar from '../../../components/avatar'
|
||||
import Text from '../../../components/text'
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp'
|
||||
import { makeGetAccount } from '../../../selectors'
|
||||
|
||||
class MessagesListItem extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
composeFocused: false,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
account,
|
||||
intl,
|
||||
selected,
|
||||
} = this.props
|
||||
|
||||
const buttonClasses = CX({
|
||||
d: 1,
|
||||
pt2: 1,
|
||||
pr5: 1,
|
||||
noUnderline: 1,
|
||||
overflowHidden: 1,
|
||||
flexNormal: 1,
|
||||
flexRow: 1,
|
||||
aiStart: 1,
|
||||
aiCenter: 1,
|
||||
})
|
||||
|
||||
const containerClasses = CX({
|
||||
d: 1,
|
||||
bgSubtle_onHover: 1,
|
||||
borderBottom1PX: 1,
|
||||
borderColorSecondary: 1,
|
||||
noUnderline: 1,
|
||||
})
|
||||
|
||||
const innerContainerClasses = CX({
|
||||
d: 1,
|
||||
flexRow: 1,
|
||||
aiStart: 1,
|
||||
aiCenter: 0,
|
||||
px15: 1,
|
||||
py15: 1,
|
||||
borderRight4PX: selected,
|
||||
borderColorBrand: selected,
|
||||
})
|
||||
|
||||
const avatarSize = 49
|
||||
const content = { __html: 'REEEE i heard you have the sauce?' }
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
className={containerClasses}
|
||||
title={account.get('acct')}
|
||||
to={`/messages/conversation-id`}
|
||||
>
|
||||
<div className={innerContainerClasses}>
|
||||
|
||||
<Avatar account={account} size={avatarSize} noHover />
|
||||
|
||||
<div className={[_s.d, _s.pl10, _s.overflowHidden, _s.flexNormal].join(' ')}>
|
||||
<div className={[_s.d, _s.flexRow, _s.aiCenter].join(' ')}>
|
||||
<div className={buttonClasses}>
|
||||
<div className={_s.maxW100PC42PX}>
|
||||
<DisplayName account={account} noHover />
|
||||
</div>
|
||||
<Text size='extraSmall' color='secondary' className={_s.mlAuto}>
|
||||
May 1
|
||||
{ /* <RelativeTimestamp timestamp={'2020-20-10'} /> */ }
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={[_s.py5, _s.dangerousContent].join(' ')} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
</div>
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: makeGetAccount()(state, '1'),
|
||||
})
|
||||
|
||||
MessagesListItem.propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
selected: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(MessagesListItem)
|
||||
Reference in New Issue
Block a user