Progress on DMs

Progress on DMs
This commit is contained in:
mgabdev
2020-12-19 01:33:33 -05:00
parent 47cd60f851
commit 7ec426e3d8
38 changed files with 447 additions and 197 deletions

View File

@@ -20,7 +20,7 @@ export const UNBLOCK_CHAT_MESSAGER_REQUEST = 'UNBLOCK_CHAT_MESSAGER_REQUEST'
export const UNBLOCK_CHAT_MESSAGER_SUCCESS = 'UNBLOCK_CHAT_MESSAGER_SUCCESS'
export const UNBLOCK_CHAT_MESSAGER_FAIL = 'UNBLOCK_CHAT_MESSAGER_FAIL'
export const IS_CHAT_MESSENGER_BLOCKED_SUCCESS = 'IS_CHAT_MESSENGER_BLOCKED_SUCCESS'
export const FETCH_CHAT_MESSENGER_BLOCKING_RELATIONSHIPS_SUCCESS = 'FETCH_CHAT_MESSENGER_BLOCKING_RELATIONSHIPS_SUCCESS'
//
@@ -36,13 +36,12 @@ export const UNMUTE_CHAT_CONVERSATION_FAIL = 'UNMUTE_CHAT_CONVERSATION_FAIL'
*
*/
export const blockChatMessenger = (accountId) => (dispatch, getState) => {
console.log("blockChatMessenger:", accountId)
if (!me || !accountId) return
dispatch(blockChatMessengerRequest(accountId))
api(getState).post(`/api/v1/chat_conversation_accounts/${accountId}/block_messenger`).then((response) => {
dispatch(blockChatMessengerSuccess())
api(getState).post(`/api/v1/chat_conversation_accounts/_/block_messenger`, { account_id: accountId }).then((response) => {
dispatch(blockChatMessengerSuccess(response.data))
}).catch((error) => {
dispatch(blockChatMessengerFail(accountId, error))
})
@@ -53,8 +52,9 @@ const blockChatMessengerRequest = (accountId) => ({
accountId,
})
const blockChatMessengerSuccess = () => ({
const blockChatMessengerSuccess = (data) => ({
type: BLOCK_CHAT_MESSAGER_SUCCESS,
data,
showToast: true,
})
@@ -73,8 +73,8 @@ export const unblockChatMessenger = (accountId) => (dispatch, getState) => {
dispatch(unblockChatMessengerRequest(accountId))
api(getState).post(`/api/v1/chat_conversation_accounts/${accountId}/unblock_messenger`).then((response) => {
dispatch(unblockChatMessengerSuccess())
api(getState).post(`/api/v1/chat_conversation_accounts/_/unblock_messenger`, { account_id: accountId }).then((response) => {
dispatch(unblockChatMessengerSuccess(response.data))
}).catch((error) => {
dispatch(unblockChatMessengerFail(accountId, error))
})
@@ -85,8 +85,9 @@ const unblockChatMessengerRequest = (accountId) => ({
accountId,
})
const unblockChatMessengerSuccess = () => ({
const unblockChatMessengerSuccess = (data) => ({
type: UNBLOCK_CHAT_MESSAGER_SUCCESS,
data,
showToast: true,
})
@@ -101,16 +102,16 @@ const unblockChatMessengerFail = (accountId, error) => ({
* @description Check if a chat messenger is blocked by the current user account.
* @param {String} accountId
*/
export const isChatMessengerBlocked = (accountId) => (dispatch, getState) => {
export const fetchMessengerBlockingRelationships = (accountId) => (dispatch, getState) => {
if (!me || !accountId) return
api(getState).post(`/api/v1/chat_conversation_accounts/${accountId}/is_messenger_blocked`).then((response) => {
dispatch(isChatMessengerBlockedSuccess(response.data))
api(getState).post(`/api/v1/chat_conversation_accounts/_/messenger_block_relationships`, { account_id: accountId }).then((response) => {
dispatch(fetchMessengerBlockingRelationshipsSuccess(response.data))
})
}
const isChatMessengerBlockedSuccess = (data) => ({
type: IS_CHAT_MESSENGER_BLOCKED_SUCCESS,
const fetchMessengerBlockingRelationshipsSuccess = (data) => ({
type: FETCH_CHAT_MESSENGER_BLOCKING_RELATIONSHIPS_SUCCESS,
data,
})
@@ -193,7 +194,7 @@ export const muteChatConversation = (chatConversationId) => (dispatch, getState)
api(getState).post(`/api/v1/chat_conversation_accounts/${chatConversationId}/mute_chat_conversation`).then((response) => {
dispatch(muteChatConversationSuccess(response.data))
}).catch((error) => {
dispatch(muteChatMessengerFail(error))
dispatch(muteChatConversationFail(error))
})
}

View File

@@ -389,9 +389,9 @@ export const readChatConversationFail = () => ({
*
*/
export const setChatConversationExpiration = (chatConversationId, expiration) => (dispatch, getState) => {
if (!me|| !chatConversationId || !expiration) return
if (!me|| !chatConversationId) return
dispatch(setChatConversationExpirationFetch(chatConversation))
dispatch(setChatConversationExpirationFetch(chatConversationId))
api(getState).post(`/api/v1/chat_conversation/${chatConversationId}/set_expiration_policy`, {
expiration,
@@ -400,18 +400,18 @@ export const setChatConversationExpiration = (chatConversationId, expiration) =>
}).catch((error) => dispatch(setChatConversationExpirationFail(error)))
}
export const setChatConversationExpirationFetch = (chatConversation) => ({
export const setChatConversationExpirationFetch = (chatConversationId) => ({
type: SET_CHAT_CONVERSATION_EXPIRATION_REQUEST,
chatConversation,
chatConversationId,
})
export const setChatConversationExpirationSuccess = (chatConversation) => ({
type: SET_CHAT_CONVERSATION_EXPIRATION_REQUEST,
type: SET_CHAT_CONVERSATION_EXPIRATION_SUCCESS,
chatConversation,
})
export const setChatConversationExpirationFail = (error) => ({
type: SET_CHAT_CONVERSATION_EXPIRATION_REQUEST,
type: SET_CHAT_CONVERSATION_EXPIRATION_FAIL,
error,
})

View File

@@ -113,12 +113,12 @@ const deleteChatMessageFail = (error) => ({
export const purgeChatMessages = (chatConversationId) => (dispatch, getState) => {
if (!me || !chatConversationId) return
dispatch(deleteChatMessagesRequest(chatConversationId))
dispatch(purgeChatMessagesRequest(chatConversationId))
api(getState).delete(`/api/v1/chat_conversations/messages/${chatConversationId}/destroy_all`).then((response) => {
dispatch(deleteChatMessagesSuccess(response.data))
dispatch(purgeChatMessagesSuccess(chatConversationId))
}).catch((error) => {
dispatch(deleteChatMessagesFail(error))
dispatch(purgeChatMessagesFail(error))
})
}

View File

@@ -21,9 +21,9 @@ import { openModal, closeModal } from './modal'
import {
MODAL_COMPOSE,
EXPIRATION_OPTION_5_MINUTES,
EXPIRATION_OPTION_60_MINUTES,
EXPIRATION_OPTION_1_HOUR,
EXPIRATION_OPTION_6_HOURS,
EXPIRATION_OPTION_24_HOURS,
EXPIRATION_OPTION_1_DAY,
EXPIRATION_OPTION_3_DAYS,
EXPIRATION_OPTION_7_DAYS,
} from '../constants'
@@ -345,23 +345,7 @@ export const submitCompose = (groupId, replyToId = null, router, isStandalone, a
let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null)
if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate()
let expires_at = getState().getIn(['compose', 'expires_at'], null)
if (expires_at) {
if (expires_at === EXPIRATION_OPTION_5_MINUTES) {
expires_at = moment.utc().add('5', 'minute').toDate()
} else if (expires_at === EXPIRATION_OPTION_60_MINUTES) {
expires_at = moment.utc().add('60', 'minute').toDate()
} else if (expires_at === EXPIRATION_OPTION_6_HOURS) {
expires_at = moment.utc().add('6', 'hour').toDate()
} else if (expires_at === EXPIRATION_OPTION_24_HOURS) {
expires_at = moment.utc().add('24', 'hour').toDate()
} else if (expires_at === EXPIRATION_OPTION_3_DAYS) {
expires_at = moment.utc().add('3', 'day').toDate()
} else if (expires_at === EXPIRATION_OPTION_7_DAYS) {
expires_at = moment.utc().add('7', 'day').toDate()
}
}
const expires_at = getState().getIn(['compose', 'expires_at'], null)
if (isMobile(window.innerWidth) && router && isStandalone) {
router.history.goBack()

View File

@@ -3,12 +3,12 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { closePopover } from '../../actions/popover'
import { changeExpiresAt } from '../../actions/compose'
import { setChatConversationExpiration } from '../../actions/chat_conversations'
import {
EXPIRATION_OPTION_5_MINUTES,
EXPIRATION_OPTION_60_MINUTES,
EXPIRATION_OPTION_1_HOUR,
EXPIRATION_OPTION_6_HOURS,
EXPIRATION_OPTION_24_HOURS,
EXPIRATION_OPTION_1_DAY,
EXPIRATION_OPTION_3_DAYS,
EXPIRATION_OPTION_7_DAYS,
} from '../../constants'
@@ -19,7 +19,7 @@ import Text from '../text'
class ChatConversationExpirationOptionsPopover extends React.PureComponent {
handleOnSetExpiration = (expiresAt) => {
this.props.onChangeExpiresAt(expiresAt)
this.props.onSetChatConversationExpiration(expiresAt)
this.handleOnClosePopover()
}
@@ -29,66 +29,62 @@ class ChatConversationExpirationOptionsPopover extends React.PureComponent {
render() {
const {
chatConversationId,
expiresAtValue,
intl,
isXS,
} = this.props
console.log("expiresAtValue:", expiresAtValue)
if (!chatConversationId) return <div/>
const listItems = [
{
hideArrow: true,
title: 'None',
onClick: () => this.handleOnSetStatusExpiration(null),
onClick: () => this.handleOnSetExpiration(null),
isActive: !expiresAtValue,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 5 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_5_MINUTES),
onClick: () => this.handleOnSetExpiration(EXPIRATION_OPTION_5_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_5_MINUTES,
},
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 60 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_60_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_60_MINUTES,
onClick: () => this.handleOnSetExpiration(EXPIRATION_OPTION_1_HOUR),
isActive: expiresAtValue === EXPIRATION_OPTION_1_HOUR,
},
{
hideArrow: true,
title: '6 hours',
title: intl.formatMessage(messages.hours, { number: 6 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_6_HOURS),
onClick: () => this.handleOnSetExpiration(EXPIRATION_OPTION_6_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_6_HOURS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.hours, { number: 24 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_24_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_24_HOURS,
onClick: () => this.handleOnSetExpiration(EXPIRATION_OPTION_1_DAY),
isActive: expiresAtValue === EXPIRATION_OPTION_1_DAY,
},
{
hideArrow: true,
title: '3 days',
title: intl.formatMessage(messages.days, { number: 3 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_3_DAYS),
onClick: () => this.handleOnSetExpiration(EXPIRATION_OPTION_3_DAYS),
isActive: expiresAtValue === EXPIRATION_OPTION_3_DAYS,
},
{
hideArrow: true,
title: intl.formatMessage(messages.days, { number: 7 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_7_DAYS),
onClick: () => this.handleOnSetExpiration(EXPIRATION_OPTION_7_DAYS),
isActive: expiresAtValue === EXPIRATION_OPTION_7_DAYS,
},
]
if (expiresAtValue) {
listItems.unshift({
hideArrow: true,
title: 'Remove expiration',
onClick: () => this.handleOnSetStatusExpiration(null),
},)
}
return (
<PopoverLayout
width={210}
@@ -113,24 +109,24 @@ const messages = defineMessages({
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
})
const mapStateToProps = (state) => ({
expiresAtValue: state.getIn(['compose', 'expires_at']),
const mapStateToProps = (state, { chatConversationId }) => ({
expiresAtValue: state.getIn(['chat_conversations', chatConversationId, 'chat_message_expiration_policy']),
})
const mapDispatchToProps = (dispatch) => ({
onChangeExpiresAt(expiresAt) {
dispatch(changeExpiresAt(expiresAt))
},
const mapDispatchToProps = (dispatch, { chatConversationId }) => ({
onClosePopover() {
dispatch(closePopover())
},
onSetChatConversationExpiration(expiration) {
dispatch(setChatConversationExpiration(chatConversationId, expiration))
},
})
ChatConversationExpirationOptionsPopover.defaultProps = {
expiresAtValue: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired,
isXS: PropTypes.bool,
onChangeExpiresAt: PropTypes.func.isRequired,
onSetChatConversationExpiration: PropTypes.func.isRequired,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ChatConversationExpirationOptionsPopover))

View File

@@ -6,6 +6,10 @@ import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import { openModal } from '../../actions/modal'
import { hideChatConversation } from '../../actions/chat_conversations'
import {
muteChatConversation,
unmuteChatConversation,
} from '../../actions/chat_conversation_accounts'
import { purgeChatMessages } from '../../actions/chat_messages'
import { MODAL_PRO_UPGRADE } from '../../constants'
import { me } from '../../initial_state'
@@ -21,8 +25,12 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
this.handleOnClosePopover()
}
handleOnUnmute = () => {
this.props.onUnute()
handleOnMute = () => {
if (this.props.isMuted) {
this.props.onUnmute()
} else {
this.props.onMute()
}
this.handleOnClosePopover()
}
@@ -30,7 +38,7 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
if (!this.props.isPro) {
this.props.openProUpgradeModal()
} else {
this.props.onPurge(this.props.chatConversationId)
this.props.onPurge()
}
this.handleOnClosePopover()
@@ -44,6 +52,7 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
const {
intl,
isXS,
isMuted,
} = this.props
const items = [
@@ -55,9 +64,9 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
},
{
hideArrow: true,
title: 'Mute Conversation',
subtitle: "Don't get notified of new messages",
onClick: () => this.handleOnHide(),
title: isMuted ? 'Unmute Conversation' : 'Mute Conversation',
subtitle: isMuted ? null : "Don't get notified of new messages",
onClick: () => this.handleOnMute(),
},
{},
{
@@ -86,23 +95,28 @@ class ChatConversationOptionsPopover extends ImmutablePureComponent {
const mapStateToProps = (state, { chatConversationId }) => ({
isPro: state.getIn(['accounts', me, 'is_pro']),
chatConversation: makeGetChatConversation()(state, { id: chatConversationId }),
isMuted: state.getIn(['chat_conversations', chatConversationId, 'is_muted']),
})
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = (dispatch, { chatConversationId }) => ({
openProUpgradeModal() {
dispatch(openModal(MODAL_PRO_UPGRADE))
},
onSetCommentSortingSetting(type) {
dispatch(closePopover())
},
onPurge(chatConversationId) {
onPurge() {
dispatch(purgeChatMessages(chatConversationId))
},
onHide(chatConversationId) {
onHide() {
dispatch(hideChatConversation(chatConversationId))
},
onClosePopover: () => dispatch(closePopover()),
onMute() {
dispatch(muteChatConversation(chatConversationId))
},
onUnmute() {
dispatch(unmuteChatConversation(chatConversationId))
},
onClosePopover() {
dispatch(closePopover())
},
})
ChatConversationOptionsPopover.propTypes = {

View File

@@ -4,11 +4,11 @@ import { connect } from 'react-redux'
import { closePopover } from '../../actions/popover'
import { deleteChatMessage } from '../../actions/chat_messages'
import {
fetchMessengerBlockingRelationships,
blockChatMessenger,
unblockChatMessenger,
reportChatMessage,
// reportChatMessage,
} from '../../actions/chat_conversation_accounts'
import { fetchRelationships } from '../../actions/accounts'
import { makeGetChatMessage } from '../../selectors'
import { me } from '../../initial_state'
import PopoverLayout from './popover_layout'
@@ -20,7 +20,7 @@ class ChatMessageOptionsPopover extends React.PureComponent {
componentDidMount() {
if (!this.props.isMine) {
this.props.onFetchRelationships(this.props.fromAccountId)
this.props.onFetchMessengerBlockingRelationships(this.props.fromAccountId)
}
}
@@ -33,7 +33,7 @@ class ChatMessageOptionsPopover extends React.PureComponent {
}
handleOnBlock = () => {
if (this.props.isBlocked) {
if (this.props.isChatBlocked) {
this.props.onUnblock(this.props.fromAccountId)
} else {
this.props.onBlock(this.props.fromAccountId)
@@ -48,7 +48,7 @@ class ChatMessageOptionsPopover extends React.PureComponent {
const {
isXS,
isMine,
isBlocked,
isChatBlocked,
} = this.props
const items = isMine ? [
@@ -66,8 +66,8 @@ class ChatMessageOptionsPopover extends React.PureComponent {
{},
{
hideArrow: true,
title: isBlocked ? 'Unblock Messenger' : 'Block Messenger',
subtitle: isBlocked ? '' : 'The messenger will not be able to message you.',
title: isChatBlocked ? 'Unblock Messenger' : 'Block Messenger',
subtitle: isChatBlocked ? null : 'The messenger will not be able to message you.',
onClick: () => this.handleOnBlock(),
},
]
@@ -90,7 +90,7 @@ const mapStateToProps = (state, { chatMessageId }) => {
return {
fromAccountId,
isMine: fromAccountId === me,
isBlocked: state.getIn(['relationships', fromAccountId, 'chat_blocked_by'], false),
isChatBlocked: state.getIn(['relationships', fromAccountId, 'chat_blocking'], false),
}
}
@@ -101,15 +101,19 @@ const mapDispatchToProps = (dispatch) => ({
},
onBlock(accountId) {
dispatch(blockChatMessenger(accountId))
dispatch(closePopover())
},
onUnblock(accountId) {
dispatch(unblockChatMessenger(accountId))
dispatch(closePopover())
},
onReportChatMessage(chatMessageId) {
dispatch(reportChatMessage(chatMessageId))
// : todo :
// dispatch(reportChatMessage(chatMessageId))
dispatch(closePopover())
},
onFetchRelationships(accountId) {
// dispatch(fetchRelationships(accountId))
onFetchMessengerBlockingRelationships(accountId) {
dispatch(fetchMessengerBlockingRelationships(accountId))
},
onClosePopover() {
dispatch(closePopover())
@@ -120,8 +124,9 @@ ChatMessageOptionsPopover.propTypes = {
isXS: PropTypes.bool,
isMine: PropTypes.bool,
chatMessageId: PropTypes.string.isRequired,
isBlocked: PropTypes.bool.isRequired,
isChatBlocked: PropTypes.bool.isRequired,
onDeleteChatMessage: PropTypes.func.isRequired,
onIsChatMessengerBlocked: PropTypes.func.isRequired,
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessageOptionsPopover)

View File

@@ -6,9 +6,9 @@ import { closePopover } from '../../actions/popover'
import { changeExpiresAt } from '../../actions/compose'
import {
EXPIRATION_OPTION_5_MINUTES,
EXPIRATION_OPTION_60_MINUTES,
EXPIRATION_OPTION_1_HOUR,
EXPIRATION_OPTION_6_HOURS,
EXPIRATION_OPTION_24_HOURS,
EXPIRATION_OPTION_1_DAY,
EXPIRATION_OPTION_3_DAYS,
EXPIRATION_OPTION_7_DAYS,
} from '../../constants'
@@ -50,8 +50,8 @@ class StatusExpirationOptionsPopover extends React.PureComponent {
{
hideArrow: true,
title: intl.formatMessage(messages.minutes, { number: 60 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_60_MINUTES),
isActive: expiresAtValue === EXPIRATION_OPTION_60_MINUTES,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_1_HOUR),
isActive: expiresAtValue === EXPIRATION_OPTION_1_HOUR,
},
{
hideArrow: true,
@@ -63,8 +63,8 @@ class StatusExpirationOptionsPopover extends React.PureComponent {
{
hideArrow: true,
title: intl.formatMessage(messages.hours, { number: 24 }),
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_24_HOURS),
isActive: expiresAtValue === EXPIRATION_OPTION_24_HOURS,
onClick: () => this.handleOnSetStatusExpiration(EXPIRATION_OPTION_1_DAY),
isActive: expiresAtValue === EXPIRATION_OPTION_1_DAY,
},
{
hideArrow: true,

View File

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
import Immutable from 'immutable'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import punycode from 'punycode'
import {
CX,
DEFAULT_REL,

View File

@@ -134,9 +134,9 @@ export const GAB_COM_INTRODUCE_YOURSELF_GROUP_ID = '12'
export const MIN_ACCOUNT_CREATED_AT_ONBOARDING = 1594789200000 // 2020-07-15
export const EXPIRATION_OPTION_5_MINUTES = 'five_minutes'
export const EXPIRATION_OPTION_60_MINUTES = 'one_hour'
export const EXPIRATION_OPTION_1_HOUR = 'one_hour'
export const EXPIRATION_OPTION_6_HOURS = 'six_hours'
export const EXPIRATION_OPTION_24_HOURS = 'one_day'
export const EXPIRATION_OPTION_1_DAY = 'one_day'
export const EXPIRATION_OPTION_3_DAYS = 'three_days'
export const EXPIRATION_OPTION_7_DAYS = 'one_week'

View File

@@ -7,6 +7,7 @@ import { makeGetChatConversation } from '../../../selectors'
import { setChatConversationSelected } from '../../../actions/chats'
import { CX } from '../../../constants'
import Input from '../../../components/input'
import Icon from '../../../components/icon'
import DisplayNameGroup from '../../../components/display_name_group'
import DisplayName from '../../../components/display_name'
import AvatarGroup from '../../../components/avatar_group'
@@ -35,8 +36,6 @@ class ChatConversationsListItem extends ImmutablePureComponent {
if (!chatConversation) return <div/>
console.log("chatConversation:", chatConversation)
const containerClasses = CX({
d: 1,
w100PC: 1,
@@ -66,6 +65,8 @@ class ChatConversationsListItem extends ImmutablePureComponent {
lastMessageText = lastMessageText.length >= 28 ? `${lastMessageText.substring(0, 28).trim()}...` : lastMessageText
const content = { __html: lastMessageText }
const date = !!lastMessage ? lastMessage.get('created_at') : chatConversation.get('created_at')
const isUnread = chatConversation.get('is_unread')
const isMuted = chatConversation.get('is_muted')
return (
<button
@@ -73,7 +74,13 @@ class ChatConversationsListItem extends ImmutablePureComponent {
onClick={this.handleOnClick}
>
{ chatConversation.get('is_unread') && <div className={[_s.d, _s.posAbs, _s.left0, _s.top50PC, _s.ml10, _s.mtNeg5PX, _s.circle, _s.w10PX, _s.h10PX, _s.bgBrand].join(' ')} /> }
{ isUnread && !isMuted && <div className={[_s.d, _s.posAbs, _s.left0, _s.top50PC, _s.ml10, _s.mtNeg5PX, _s.circle, _s.w10PX, _s.h10PX, _s.bgBrand].join(' ')} /> }
{
isMuted &&
<div className={[_s.d, _s.posAbs, _s.left0, _s.top50PC, _s.ml10, _s.mtNeg5PX, _s.circle, _s.w10PX, _s.h10PX, _s.bgTransparent].join(' ')}>
<Icon id='audio-mute' className={_s.cError} size='12px' />
</div>
}
<div className={innerContainerClasses}>
<AvatarGroup accounts={otherAccounts} size={avatarSize} noHover />

View File

@@ -88,7 +88,11 @@ class ChatMessagesComposeForm extends React.PureComponent {
}
render () {
const { isXS, chatConversationId } = this.props
const {
isXS,
expiresAtValue,
chatConversationId,
} = this.props
const { value } = this.state
const disabled = false
@@ -108,6 +112,22 @@ class ChatMessagesComposeForm extends React.PureComponent {
py10: 1,
})
const expireBtnClasses = CX({
d: 1,
borderRight1PX: 1,
borderColorSecondary: 1,
w40PX: 1,
h100PC: 1,
aiCenter: 1,
jcCenter: 1,
cursorPointer: 1,
outlineNone: 1,
bgSubtle: !expiresAtValue,
bgBlack: !!expiresAtValue,
})
const expireBtnIconClassName = !!expiresAtValue ? _s.cWhite : _s.cBlack
const textarea = (
<Textarea
id='chat-message-compose-input'
@@ -137,13 +157,15 @@ class ChatMessagesComposeForm extends React.PureComponent {
)
const expiresBtn = (
<button
ref={this.setExpiresBtn}
className={[_s.d, _s.bgSubtle, _s.borderRight1PX, _s.borderColorSecondary, _s.w40PX, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.cursorPointer, _s.outlineNone].join(' ')}
<Button
noClasses
buttonRef={this.setExpiresBtn}
className={expireBtnClasses}
onClick={this.handleOnExpire}
>
<Icon id='stopwatch' className={[_s.cPrimary, _s.ml2].join(' ')} size='15px' />
</button>
icon='stopwatch'
iconSize='15px'
iconClassName={expireBtnIconClassName}
/>
)
if (isXS) {
@@ -191,8 +213,9 @@ class ChatMessagesComposeForm extends React.PureComponent {
}
const mapStateToProps = (state) => ({
const mapStateToProps = (state, { chatConversationId }) => ({
isPro: state.getIn(['accounts', me, 'is_pro']),
expiresAtValue: state.getIn(['chat_conversations', chatConversationId, 'chat_message_expiration_policy']),
})
const mapDispatchToProps = (dispatch, { chatConversationId }) => ({

View File

@@ -12,9 +12,11 @@ import {
} from '../../../constants'
import { me } from '../../../initial_state'
import Input from '../../../components/input'
import Icon from '../../../components/icon'
import Avatar from '../../../components/avatar'
import Button from '../../../components/button'
import Text from '../../../components/text'
import DotTextSeperator from '../../../components/dot_text_seperator'
import { makeGetChatMessage } from '../../../selectors'
class ChatMessageItem extends ImmutablePureComponent {
@@ -30,9 +32,9 @@ class ChatMessageItem extends ImmutablePureComponent {
}
componentDidMount() {
const { lastChatMessageSameSender, lastChatMessageDate } = this.props
if (lastChatMessageDate) {
const createdAt = this.props.chatMessage.get('created_at')
const { lastChatMessageSameSender, lastChatMessageDate, chatMessage } = this.props
if (lastChatMessageDate && chatMessage) {
const createdAt = 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({
@@ -125,6 +127,12 @@ class ChatMessageItem extends ImmutablePureComponent {
displayNone: !isHovering,
})
const expirationDate = chatMessage.get('expires_at')
let timeUntilExpiration
if (!!expirationDate) {
timeUntilExpiration = moment(expirationDate).fromNow()
}
return (
<div
className={[_s.d, _s.w100PC, _s.pb10].join(' ')}
@@ -161,6 +169,15 @@ class ChatMessageItem extends ImmutablePureComponent {
<div className={lowerContainerClasses}>
<Text size='extraSmall' color='tertiary' align={alt ? 'right' : 'left'}>
{moment(createdAt).format('lll')}
{
!!expirationDate &&
<React.Fragment>
<DotTextSeperator />
<Text size='extraSmall' color='tertiary' className={_s.ml5}>Expires in {timeUntilExpiration}</Text>
<Icon id='stopwatch' size='11px' className={[_s.d, _s.ml5, _s.displayInline, _s.cSecondary].join(' ')} />
</React.Fragment>
}
</Text>
</div>
</div>

View File

@@ -47,15 +47,11 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
this.detachIntersectionObserver()
}
componentWillReceiveProps (nextProps) {
const { chatConversationId } = nextProps
if (chatConversationId !== this.props.chatConversationId) {
this.props.onExpandChatMessages(chatConversationId)
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.chatConversationId !== this.props.chatConversationId) {
this.props.onExpandChatMessages(this.props.chatConversationId)
}
// Reset the scroll position when a new child comes in in order not to
// jerk the scrollbar around if you're already scrolled down the page.
if (snapshot !== null && this.scrollContainerRef) {
@@ -69,6 +65,8 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
if (prevProps.chatMessageIds.size === 0 && this.props.chatMessageIds.size > 0 && this.scrollContainerRef) {
this.scrollContainerRef.scrollTop = this.scrollContainerRef.scrollHeight
this.props.onReadChatConversation(this.props.chatConversationId)
} else if (prevProps.chatMessageIds.size < this.props.chatMessageIds.size && this.scrollContainerRef) {
this.scrollContainerRef.scrollTop = this.scrollContainerRef.scrollHeight
}
}
@@ -265,7 +263,6 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
lastChatMessageId={lastChatMessageId}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
commentsLimited
/>
)
}

View File

@@ -27,9 +27,13 @@ class ChatSettingsSidebar extends React.PureComponent {
to: '/messages/requests',
},
{
title: 'Blocked Chats',
title: 'Blocked Chat Messengers',
to: '/messages/blocks',
},
{
title: 'Muted Conversations',
to: '/messages/muted_conversations',
},
]}
/>
</ResponsiveClassesComponent>

View File

@@ -221,6 +221,7 @@ class SwitchingArea extends React.PureComponent {
<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/muted_conversations' exact page={MessagesPage} component={ChatConversationBlockedAccounts} content={children} componentParams={{ isSettings: true }} />
<WrappedRoute path='/messages/:chatConversationId' 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' }} />

View File

@@ -7,6 +7,7 @@ import compareId from '../utils/compare_id'
import {
CHAT_MESSAGES_SEND_SUCCESS,
CHAT_MESSAGES_DELETE_REQUEST,
CHAT_MESSAGES_PURGE_SUCCESS,
} from '../actions/chat_messages'
import {
CHAT_CONVERSATION_MESSAGES_EXPAND_REQUEST,
@@ -101,6 +102,9 @@ export default function chat_conversation_messages(state = initialState, action)
case CHAT_MESSAGES_DELETE_REQUEST:
// : todo :
return state
case CHAT_MESSAGES_PURGE_SUCCESS:
// : todo :
return state
default:
return state
}

View File

@@ -16,6 +16,7 @@ import {
CHAT_CONVERSATIONS_REQUESTED_EXPAND_SUCCESS,
CHAT_CONVERSATION_REQUEST_APPROVE_SUCCESS,
CHAT_CONVERSATION_MARK_READ_SUCCESS,
SET_CHAT_CONVERSATION_EXPIRATION_SUCCESS,
} from '../actions/chat_conversations'
const initialState = ImmutableMap()
@@ -41,6 +42,7 @@ const importChatConversations = (state, chatConversations) => {
export default function chat_conversations(state = initialState, action) {
switch(action.type) {
case CHAT_CONVERSATION_REQUEST_APPROVE_SUCCESS:
case SET_CHAT_CONVERSATION_EXPIRATION_SUCCESS:
return importChatConversation(state, action.chatConversation)
case CHAT_CONVERSATIONS_APPROVED_FETCH_SUCCESS:
case CHAT_CONVERSATIONS_APPROVED_EXPAND_SUCCESS:

View File

@@ -12,6 +12,7 @@ import {
RELATIONSHIPS_FETCH_SUCCESS,
} from '../actions/accounts'
import {
FETCH_CHAT_MESSENGER_BLOCKING_RELATIONSHIPS_SUCCESS,
BLOCK_CHAT_MESSAGER_SUCCESS,
UNBLOCK_CHAT_MESSAGER_SUCCESS,
} from '../actions/chat_conversation_accounts'
@@ -39,14 +40,21 @@ export default function relationships(state = initialState, action) {
return state.setIn([action.id, 'following'], false)
case ACCOUNT_UNFOLLOW_FAIL:
return state.setIn([action.id, 'following'], true)
case BLOCK_CHAT_MESSAGER_SUCCESS:
case FETCH_CHAT_MESSENGER_BLOCKING_RELATIONSHIPS_SUCCESS:
case UNBLOCK_CHAT_MESSAGER_SUCCESS:
return state.withMutations((map) => {
if (action.data.chat_blocking !== undefined) map.setIn([data.target_id, 'chat_blocking'], action.data.chat_blocking)
if (action.data.chat_blocked_by !== undefined) map.setIn([data.target_id, 'chat_blocked_by'], action.data.chat_blocked_by)
})
case ACCOUNT_FOLLOW_SUCCESS:
case ACCOUNT_UNFOLLOW_SUCCESS:
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_UNBLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
case ACCOUNT_UNMUTE_SUCCESS:
case BLOCK_CHAT_MESSAGER_SUCCESS:
case UNBLOCK_CHAT_MESSAGER_SUCCESS:
return normalizeRelationship(state, action.relationship)
case RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships)

View File

@@ -1,3 +1,4 @@
import punycode from 'punycode'
const IDNA_PREFIX = 'xn--'
export const decodeIDNA = (domain) => {