Progress on dms, code cleanup
Progress on dms, code cleanup
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { me } from '../initial_state'
|
||||
import { fetchBlocks, expandBlocks } from '../actions/blocks'
|
||||
import Account from '../components/account'
|
||||
import Block from '../components/block'
|
||||
import BlockHeading from '../components/block_heading'
|
||||
import Divider from '../components/divider'
|
||||
import ScrollableList from '../components/scrollable_list'
|
||||
import AccountPlaceholder from '../components/placeholder/account_placeholder'
|
||||
|
||||
class MessagesBlockedAccounts extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.onFetchBlocks()
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.onExpandBlocks()
|
||||
}, 300, { leading: true })
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
accountIds,
|
||||
hasMore,
|
||||
isLoading,
|
||||
} = this.props
|
||||
|
||||
const emptyMessage = intl.formatMessage(messages.empty)
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC, _s.boxShadowNone].join(' ')}>
|
||||
<div className={[_s.d, _s.h60PX, _s.w100PC, _s.px10, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<BlockHeading title={intl.formatMessage(messages.blocks)} />
|
||||
</div>
|
||||
<ScrollableList
|
||||
scrollKey='blocked_accounts'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading}
|
||||
emptyMessage={emptyMessage}
|
||||
placeholderComponent={AccountPlaceholder}
|
||||
placeholderCount={3}
|
||||
>
|
||||
{
|
||||
accountIds && accountIds.map((id) => (
|
||||
<Account
|
||||
key={`blocked-accounts-${id}`}
|
||||
id={id}
|
||||
compact
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ScrollableList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
empty: { id: 'empty_column.blocks', defaultMessage: 'You haven\'t blocked any users yet.' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
accountIds: state.getIn(['user_lists', 'blocks', me, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'blocks', me, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'blocks', me, 'isLoading']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchBlocks: () => dispatch(fetchBlocks()),
|
||||
onExpandBlocks: () => dispatch(expandBlocks()),
|
||||
})
|
||||
|
||||
MessagesBlockedAccounts.propTypes = {
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
onExpandBlocks: PropTypes.func.isRequired,
|
||||
onFetchBlocks: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MessagesBlockedAccounts))
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { fetchChatConversationAccountSuggestions } from '../actions/chats'
|
||||
import { createChatConversation } from '../actions/chat_conversations'
|
||||
import Account from '../components/account'
|
||||
import Button from '../components/button'
|
||||
import Input from '../components/input'
|
||||
import Form from '../components/form'
|
||||
import Text from '../components/text'
|
||||
|
||||
class ChatConversationCreate extends React.PureComponent {
|
||||
|
||||
state = {
|
||||
query: '',
|
||||
}
|
||||
|
||||
onChange = (query) => {
|
||||
this.setState({ query })
|
||||
this.props.onChange(query)
|
||||
}
|
||||
|
||||
handleOnCreateChatConversation = (accountId) => {
|
||||
console.log("handleOnCreateChatConversation:", accountId)
|
||||
this.props.onCreateChatConversation(accountId)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query, suggestionsIds } = this.props
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<div className={[_s.d, _s.px15, _s.pt10].join(' ')}>
|
||||
<Input
|
||||
title='Search for a user'
|
||||
value={query}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={[_s.d, _s.pt10].join(' ')}>
|
||||
<div className={[_s.d].join(' ')}>
|
||||
<Text weight='bold' size='small' color='secondary' className={[_s.d, _s.px15, _s.ml15, _s.mt5, _s.mb15].join(' ')}>
|
||||
Search results ({suggestionsIds.size})
|
||||
</Text>
|
||||
{
|
||||
suggestionsIds &&
|
||||
suggestionsIds.map((accountId) => (
|
||||
<Account
|
||||
compact
|
||||
key={`remove-from-list-${accountId}`}
|
||||
id={accountId}
|
||||
onActionClick={() => this.handleOnCreateChatConversation(accountId)}
|
||||
actionIcon='add'
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
suggestionsIds: state.getIn(['chats', 'createChatConversationSuggestionIds']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onChange: (value) => {
|
||||
console.log("value", value)
|
||||
dispatch(fetchChatConversationAccountSuggestions(value))
|
||||
},
|
||||
onCreateChatConversation: (accountId) => {
|
||||
dispatch(createChatConversation(accountId))
|
||||
},
|
||||
})
|
||||
|
||||
ChatConversationCreate.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onCreateChatConversation: PropTypes.func.isRequired,
|
||||
isModal: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationCreate)
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { injectIntl, FormattedMessage } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { me } from '../initial_state'
|
||||
import { fetchMutes, expandMutes } from '../actions/mutes'
|
||||
import Account from '../components/account'
|
||||
import Block from '../components/block'
|
||||
import BlockHeading from '../components/block_heading'
|
||||
import ScrollableList from '../components/scrollable_list'
|
||||
|
||||
class MessagesMutedAccounts extends ImmutablePureComponent {
|
||||
|
||||
componentWillMount() {
|
||||
this.props.onFetchMutes()
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.onExpandMutes()
|
||||
}, 300, { leading: true })
|
||||
|
||||
render() {
|
||||
const {
|
||||
accountIds,
|
||||
hasMore,
|
||||
isLoading,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC, _s.boxShadowNone].join(' ')}>
|
||||
<div className={[_s.d, _s.h60PX, _s.w100PC, _s.px10, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<BlockHeading title={<FormattedMessage id='navigation_bar.mutes' defaultMessage='Muted users' />} />
|
||||
</div>
|
||||
<ScrollableList
|
||||
scrollKey='mutes'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
emptyMessage={<FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />}
|
||||
>
|
||||
{
|
||||
accountIds && accountIds.map((id) =>
|
||||
<Account
|
||||
key={`mutes-${id}`}
|
||||
id={id}
|
||||
compact
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ScrollableList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
accountIds: state.getIn(['user_lists', 'mutes', me, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'mutes', me, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'mutes', me, 'isLoading']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchMutes: () => dispatch(fetchMutes()),
|
||||
onExpandMutes: () => dispatch(expandMutes()),
|
||||
})
|
||||
|
||||
MessagesMutedAccounts.propTypes = {
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
onExpandMutes: PropTypes.func.isRequired,
|
||||
onFetchMutes: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MessagesMutedAccounts))
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import BlockHeading from '../components/block_heading'
|
||||
import ChatConversationsList from './messages/components/chat_conversations_list'
|
||||
|
||||
class ChatConversationRequests extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC, _s.boxShadowNone].join(' ')}>
|
||||
<div className={[_s.d, _s.h60PX, _s.w100PC, _s.px10, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<BlockHeading title={'Message Requests'} />
|
||||
</div>
|
||||
<ChatConversationsList source='requested' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChatConversationRequests
|
||||
@@ -157,7 +157,7 @@ class PollFormOption extends ImmutablePureComponent {
|
||||
|
||||
<Input
|
||||
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
||||
maxLength={25}
|
||||
maxLength={160}
|
||||
value={title}
|
||||
onChange={this.handleOptionTitleChange}
|
||||
/>
|
||||
|
||||
@@ -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)
|
||||
@@ -1,88 +1,35 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { makeGetAccount } from '../../selectors'
|
||||
import Text from '../../components/text'
|
||||
import Button from '../../components/button'
|
||||
import Avatar from '../../components/avatar'
|
||||
import DisplayName from '../../components/display_name'
|
||||
import Input from '../../components/input'
|
||||
import EmojiPickerButton from '../compose/components/emoji_picker_button'
|
||||
import UploadButton from '../compose/components/media_upload_button'
|
||||
import MessageItem from './components/message_item'
|
||||
|
||||
// import MessagesContainer from './containers/messages_container'
|
||||
|
||||
import ChatEmptyMessageBlock from './components/chat_conversations_empty_block'
|
||||
import ChatMessageHeader from './components/chat_message_header'
|
||||
import ChatMessageScrollingList from './components/chat_message_scrolling_list'
|
||||
import ChatMessagesComposeForm from './components/chat_message_compose_form'
|
||||
|
||||
class Messages extends React.PureComponent {
|
||||
|
||||
render () {
|
||||
const { account } = this.props
|
||||
const {
|
||||
account,
|
||||
selectedChatConversationId,
|
||||
chatConverationIsRequest,
|
||||
} = this.props
|
||||
|
||||
const selectedMessage = true
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.bgPrimary, _s.h100PC, _s.w100PC].join(' ')}>
|
||||
{
|
||||
!selectedMessage &&
|
||||
<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}>
|
||||
<Text color='inherit' weight='bold' className={_s.px15}>
|
||||
New Message
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
!selectedChatConversationId &&
|
||||
<ChatEmptyMessageBlock />
|
||||
}
|
||||
{
|
||||
selectedMessage &&
|
||||
!!selectedChatConversationId &&
|
||||
<div className={[_s.d, _s.h100PC, _s.w100PC].join(' ')}>
|
||||
<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.px15, _s.py5].join(' ')}>
|
||||
<Avatar account={account} size={34} />
|
||||
<div className={[_s.d, _s.pl10, _s.maxW100PC86PX, _s.overflowHidden].join(' ')}>
|
||||
<DisplayName account={account} isMultiline />
|
||||
</div>
|
||||
<Button
|
||||
isNarrow
|
||||
onClick={undefined}
|
||||
color='brand'
|
||||
backgroundColor='none'
|
||||
className={_s.mlAuto}
|
||||
icon='more'
|
||||
iconSize='18px'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={[_s.d, _s.posAbs, _s.bottom60PX, _s.left0, _s.right0, _s.px15, _s.py15, _s.top60PX, _s.w100PC, _s.overflowYScroll].join(' ')}>
|
||||
<MessageItem />
|
||||
<MessageItem />
|
||||
<MessageItem alt />
|
||||
<MessageItem />
|
||||
<MessageItem alt />
|
||||
<MessageItem alt />
|
||||
<MessageItem />
|
||||
<MessageItem />
|
||||
<MessageItem />
|
||||
<MessageItem alt />
|
||||
<MessageItem />
|
||||
</div>
|
||||
|
||||
<div className={[_s.d, _s.posAbs, _s.bottom0, _s.left0, _s.right0, _s.flexRow, _s.aiCenter, _s.h60PX, _s.w100PC, _s.borderTop1PX, _s.borderColorSecondary, _s.px15, _s.py5].join(' ')}>
|
||||
<EmojiPickerButton />
|
||||
<UploadButton />
|
||||
<div className={[_s.d, _s.px15, _s.flexGrow1].join(' ')}>
|
||||
<Input
|
||||
placeholder='Type a message...'
|
||||
/>
|
||||
</div>
|
||||
<Button>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
<ChatMessageHeader chatConversationId={selectedChatConversationId} />
|
||||
<ChatMessageScrollingList chatConversationId={selectedChatConversationId} />
|
||||
{
|
||||
!chatConverationIsRequest &&
|
||||
<ChatMessagesComposeForm chatConversationId={selectedChatConversationId} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -91,13 +38,18 @@ class Messages extends React.PureComponent {
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: makeGetAccount()(state, '1'),
|
||||
})
|
||||
const mapStateToProps = (state, props) => {
|
||||
const selectedChatConversationId = state.getIn(['chats', 'selectedChatConversationId'], null)
|
||||
const chatConverationIsRequest = selectedChatConversationId ? !state.getIn(['chat_conversations', selectedChatConversationId, 'is_approved'], null) : false
|
||||
return {
|
||||
selectedChatConversationId,
|
||||
chatConverationIsRequest,
|
||||
}
|
||||
}
|
||||
|
||||
Messages.propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
selected: PropTypes.bool,
|
||||
selectedChatConversationId: PropTypes.string,
|
||||
chatConverationIsRequest: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Messages)
|
||||
87
app/javascript/gabsocial/features/messages_settings.js
Normal file
87
app/javascript/gabsocial/features/messages_settings.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { injectIntl, FormattedMessage } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { me } from '../initial_state'
|
||||
import { fetchMutes, expandMutes } from '../actions/mutes'
|
||||
import Account from '../components/account'
|
||||
import BlockHeading from '../components/block_heading'
|
||||
import Button from '../components/button'
|
||||
import Form from '../components/form'
|
||||
import Switch from '../components/switch'
|
||||
import Text from '../components/text'
|
||||
import Divider from '../components/divider'
|
||||
|
||||
class MessagesSettings extends ImmutablePureComponent {
|
||||
|
||||
componentWillMount() {
|
||||
this.props.onFetchMutes()
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.onExpandMutes()
|
||||
}, 300, { leading: true })
|
||||
|
||||
render() {
|
||||
const {
|
||||
accountIds,
|
||||
hasMore,
|
||||
isLoading,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.w100PC, _s.boxShadowNone].join(' ')}>
|
||||
<div className={[_s.d, _s.h60PX, _s.w100PC, _s.px10, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<BlockHeading title={'Chat Preferences'} />
|
||||
</div>
|
||||
|
||||
<div className={[_s.d, _s.px15, _s.py15].join(' ')}>
|
||||
<Form>
|
||||
<Switch
|
||||
label='Restrict messages from people you dont follow'
|
||||
checked={true}
|
||||
onChange={this.handleLockedChange}
|
||||
/>
|
||||
<div className={[_s.d, _s.w100PC, _s.my10, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')} />
|
||||
<Switch
|
||||
label='Show when you are active'
|
||||
checked={false}
|
||||
onChange={this.handleLockedChange}
|
||||
/>
|
||||
<div className={[_s.d, _s.w100PC, _s.my10, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')} />
|
||||
<Switch
|
||||
label='Notification sound enabled'
|
||||
checked={false}
|
||||
onChange={this.handleLockedChange}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
accountIds: state.getIn(['user_lists', 'mutes', me, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'mutes', me, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'mutes', me, 'isLoading']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchMutes: () => dispatch(fetchMutes()),
|
||||
onExpandMutes: () => dispatch(expandMutes()),
|
||||
})
|
||||
|
||||
MessagesSettings.propTypes = {
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
onExpandMutes: PropTypes.func.isRequired,
|
||||
onFetchMutes: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MessagesSettings))
|
||||
@@ -12,7 +12,7 @@ import Block from '../components/block'
|
||||
import BlockHeading from '../components/block_heading'
|
||||
import ScrollableList from '../components/scrollable_list'
|
||||
|
||||
class Mutes extends ImmutablePureComponent {
|
||||
class MutedAccounts extends ImmutablePureComponent {
|
||||
|
||||
componentWillMount() {
|
||||
this.props.onFetchMutes()
|
||||
@@ -66,7 +66,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
onExpandMutes: () => dispatch(expandMutes()),
|
||||
})
|
||||
|
||||
Mutes.propTypes = {
|
||||
MutedAccounts.propTypes = {
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
@@ -74,4 +74,4 @@ Mutes.propTypes = {
|
||||
onFetchMutes: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Mutes))
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MutedAccounts))
|
||||
@@ -56,6 +56,10 @@ import {
|
||||
Assets,
|
||||
BlockedAccounts,
|
||||
BookmarkedStatuses,
|
||||
ChatConversationCreate,
|
||||
ChatConversationRequests,
|
||||
ChatConversationBlockedAccounts,
|
||||
ChatConversationMutedAccounts,
|
||||
CommunityTimeline,
|
||||
Compose,
|
||||
DMCA,
|
||||
@@ -86,7 +90,8 @@ import {
|
||||
ListEdit,
|
||||
ListTimeline,
|
||||
Messages,
|
||||
Mutes,
|
||||
MessagesSettings,
|
||||
MutedAccounts,
|
||||
News,
|
||||
NewsView,
|
||||
Notifications,
|
||||
@@ -198,8 +203,12 @@ class SwitchingArea extends React.PureComponent {
|
||||
<WrappedRoute path='/news' exact publicRoute page={NewsPage} component={News} content={children} componentParams={{ title: 'News' }} />
|
||||
<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} />
|
||||
<WrappedRoute path='/messages/:conversationId' exact page={MessagesPage} component={Messages} content={children} />
|
||||
<WrappedRoute path='/messages' exact page={MessagesPage} component={Messages} content={children} componentParams={{ source: 'approved' }} />
|
||||
<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 }} />
|
||||
<WrappedRoute path='/messages/mutes' exact page={MessagesPage} component={ChatConversationMutedAccounts} content={children} componentParams={{ isSettings: true }} />
|
||||
<WrappedRoute path='/messages/:conversationId' exact page={MessagesPage} component={Messages} content={children} componentParams={{ source: 'approved' }} />
|
||||
|
||||
<WrappedRoute path='/timeline/all' exact page={CommunityPage} component={CommunityTimeline} content={children} componentParams={{ title: 'Community Feed' }} />
|
||||
<WrappedRoute path='/timeline/pro' exact page={ProPage} component={ProTimeline} content={children} componentParams={{ title: 'Pro Feed' }} />
|
||||
@@ -244,39 +253,24 @@ class SwitchingArea extends React.PureComponent {
|
||||
<WrappedRoute path='/search/links' exact page={SearchPage} component={Search} content={children} />
|
||||
|
||||
<WrappedRoute path='/settings/blocks' exact page={SettingsPage} component={BlockedAccounts} content={children} componentParams={{ title: 'Blocked Users' }} />
|
||||
<WrappedRoute path='/settings/mutes' exact page={SettingsPage} component={Mutes} content={children} componentParams={{ title: 'Muted Users' }} />
|
||||
<WrappedRoute path='/settings/mutes' exact page={SettingsPage} component={MutedAccounts} content={children} componentParams={{ title: 'Muted Users' }} />
|
||||
|
||||
<Redirect from='/@:username' to='/:username' exact />
|
||||
<WrappedRoute path='/:username' publicRoute exact page={ProfilePage} component={AccountTimeline} content={children} />
|
||||
|
||||
<Redirect from='/@:username/comments' to='/:username/comments' />
|
||||
<WrappedRoute path='/:username/comments' page={ProfilePage} component={AccountTimeline} content={children} componentParams={{ commentsOnly: true }} />
|
||||
|
||||
<Redirect from='/@:username/followers' to='/:username/followers' />
|
||||
<WrappedRoute path='/:username/followers' page={ProfilePage} component={Followers} content={children} />
|
||||
|
||||
<Redirect from='/@:username/following' to='/:username/following' />
|
||||
<WrappedRoute path='/:username/following' page={ProfilePage} component={Following} content={children} />
|
||||
|
||||
<Redirect from='/@:username/media' to='/:username/photos' />
|
||||
<Redirect from='/@:username/photos' to='/:username/photos' />
|
||||
<Redirect from='/:username/media' to='/:username/photos' />
|
||||
<WrappedRoute path='/:username/photos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} />
|
||||
<WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'video' }} />
|
||||
|
||||
<Redirect from='/@:username/likes' to='/:username/likes' />
|
||||
<WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} />
|
||||
|
||||
<Redirect from='/@:username/bookmarks' to='/:username/bookmarks' />
|
||||
<WrappedRoute path='/:username/bookmarks' page={ProfilePage} component={BookmarkedStatuses} content={children} />
|
||||
|
||||
<Redirect from='/@:username/posts/:statusId' to='/:username/posts/:statusId' exact />
|
||||
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} />
|
||||
|
||||
<Redirect from='/@:username/posts/:statusId/reposts' to='/:username/posts/:statusId/reposts' />
|
||||
<WrappedRoute path='/:username/posts/:statusId/reposts' publicRoute page={ModalPage} component={StatusReposts} content={children} componentParams={{ title: 'Reposts' }} />
|
||||
|
||||
<Redirect from='/@:username/posts/:statusId/likes' to='/:username/posts/:statusId/likes' />
|
||||
<WrappedRoute path='/:username/posts/:statusId/likes' page={ModalPage} component={StatusLikes} content={children} componentParams={{ title: 'Likes' }} />
|
||||
|
||||
<WrappedRoute page={ErrorPage} component={GenericNotFound} content={children} />
|
||||
|
||||
@@ -7,6 +7,12 @@ export function BlockAccountModal() { return import(/* webpackChunkName: "compon
|
||||
export function BlockedAccounts() { return import(/* webpackChunkName: "features/blocked_accounts" */'../../blocked_accounts') }
|
||||
export function BookmarkedStatuses() { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses') }
|
||||
export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') }
|
||||
export function ChatConversationBlockedAccounts() { return import(/* webpackChunkName: "features/chat_conversation_blocked_accounts" */'../../chat_conversation_blocked_accounts') }
|
||||
export function ChatConversationCreate() { return import(/* webpackChunkName: "features/chat_conversation_create" */'../../chat_conversation_create') }
|
||||
export function ChatConversationCreateModal() { return import(/* webpackChunkName: "components/chat_conversation_create_modal" */'../../../components/modal/chat_conversation_create_modal') }
|
||||
export function ChatConversationDeleteModal() { return import(/* webpackChunkName: "components/chat_conversation_delete_modal" */'../../../components/modal/chat_conversation_delete_modal') }
|
||||
export function ChatConversationMutedAccounts() { return import(/* webpackChunkName: "features/chat_conversation_muted_accounts" */'../../chat_conversation_muted_accounts') }
|
||||
export function ChatConversationRequests() { return import(/* webpackChunkName: "features/chat_conversation_requests" */'../../chat_conversation_requests') }
|
||||
export function CommentSortingOptionsPopover() { return import(/* webpackChunkName: "components/comment_sorting_options_popover" */'../../../components/popover/comment_sorting_options_popover') }
|
||||
export function CommunityTimeline() { return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline') }
|
||||
export function CommunityTimelineSettingsModal() { return import(/* webpackChunkName: "components/community_timeline_settings_modal" */'../../../components/modal/community_timeline_settings_modal') }
|
||||
@@ -78,7 +84,8 @@ export function MediaGallery() { return import(/* webpackChunkName: "components/
|
||||
export function MediaGalleryPanel() { return import(/* webpackChunkName: "components/media_gallery_panel" */'../../../components/panel/media_gallery_panel') }
|
||||
export function MediaModal() { return import(/* webpackChunkName: "components/media_modal" */'../../../components/modal/media_modal') }
|
||||
export function Messages() { return import(/* webpackChunkName: "features/messages" */'../../messages') }
|
||||
export function Mutes() { return import(/* webpackChunkName: "features/mutes" */'../../mutes') }
|
||||
export function MessagesSettings() { return import(/* webpackChunkName: "features/messages_settings" */'../../messages_settings') }
|
||||
export function MutedAccounts() { return import(/* webpackChunkName: "features/muted_accounts" */'../../muted_accounts') }
|
||||
export function MuteModal() { return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/mute_modal') }
|
||||
export function NavSettingsPopover() { return import(/* webpackChunkName: "modals/nav_settings_popover" */'../../../components/popover/nav_settings_popover') }
|
||||
export function News() { return import(/* webpackChunkName: "features/news" */'../../news') }
|
||||
|
||||
Reference in New Issue
Block a user