2019-06-09 19:07:23 +09:00
import PropTypes from 'prop-types' ;
2024-01-28 22:53:08 +09:00
import { useCallback , useState } from 'react' ;
2023-05-28 23:38:10 +09:00
2024-01-28 22:53:08 +09:00
import { defineMessages , useIntl , FormattedMessage } from 'react-intl' ;
2023-05-28 23:38:10 +09:00
import classNames from 'classnames' ;
2024-01-28 22:53:08 +09:00
import { useHistory } from 'react-router-dom' ;
2023-05-28 23:38:10 +09:00
2024-01-28 22:53:08 +09:00
import { createSelector } from '@reduxjs/toolkit' ;
2019-06-09 19:07:23 +09:00
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2024-01-28 22:53:08 +09:00
import { useDispatch , useSelector } from 'react-redux' ;
2023-05-28 23:38:10 +09:00
import { HotKeys } from 'react-hotkeys' ;
2024-01-16 19:27:26 +09:00
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react' ;
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react' ;
2024-01-28 22:53:08 +09:00
import { replyCompose } from 'flavours/glitch/actions/compose' ;
import { markConversationRead , deleteConversation } from 'flavours/glitch/actions/conversations' ;
import { openModal } from 'flavours/glitch/actions/modal' ;
import { muteStatus , unmuteStatus , revealStatus , hideStatus } from 'flavours/glitch/actions/statuses' ;
2019-09-22 03:01:16 +09:00
import AttachmentList from 'flavours/glitch/components/attachment_list' ;
import AvatarComposite from 'flavours/glitch/components/avatar_composite' ;
2023-05-09 10:11:56 +09:00
import { IconButton } from 'flavours/glitch/components/icon_button' ;
2024-01-14 22:15:23 +09:00
import { Permalink } from 'flavours/glitch/components/permalink' ;
2023-05-09 10:11:56 +09:00
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp' ;
2023-05-28 23:38:10 +09:00
import StatusContent from 'flavours/glitch/components/status_content' ;
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container' ;
2022-10-11 17:17:04 +09:00
import { autoPlayGif } from 'flavours/glitch/initial_state' ;
2024-01-28 22:53:08 +09:00
import { makeGetStatus } from 'flavours/glitch/selectors' ;
2023-05-28 23:38:10 +09:00
2019-09-22 03:01:16 +09:00
const messages = defineMessages ( {
more : { id : 'status.more' , defaultMessage : 'More' } ,
open : { id : 'conversation.open' , defaultMessage : 'View conversation' } ,
reply : { id : 'status.reply' , defaultMessage : 'Reply' } ,
markAsRead : { id : 'conversation.mark_as_read' , defaultMessage : 'Mark as read' } ,
delete : { id : 'conversation.delete' , defaultMessage : 'Delete conversation' } ,
muteConversation : { id : 'status.mute_conversation' , defaultMessage : 'Mute conversation' } ,
unmuteConversation : { id : 'status.unmute_conversation' , defaultMessage : 'Unmute conversation' } ,
2024-01-28 22:53:08 +09:00
replyConfirm : { id : 'confirmations.reply.confirm' , defaultMessage : 'Reply' } ,
replyMessage : { id : 'confirmations.reply.message' , defaultMessage : 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' } ,
2019-09-22 03:01:16 +09:00
} ) ;
2024-01-28 22:53:08 +09:00
const getAccounts = createSelector (
( state ) => state . get ( 'accounts' ) ,
( _ , accountIds ) => accountIds ,
( accounts , accountIds ) =>
accountIds . map ( id => accounts . get ( id ) )
) ;
const getStatus = makeGetStatus ( ) ;
export const Conversation = ( { conversation , scrollKey , onMoveUp , onMoveDown } ) => {
const id = conversation . get ( 'id' ) ;
const unread = conversation . get ( 'unread' ) ;
const lastStatusId = conversation . get ( 'last_status' ) ;
const accountIds = conversation . get ( 'accounts' ) ;
const intl = useIntl ( ) ;
const dispatch = useDispatch ( ) ;
const history = useHistory ( ) ;
const lastStatus = useSelector ( state => getStatus ( state , { id : lastStatusId } ) ) ;
const accounts = useSelector ( state => getAccounts ( state , accountIds ) ) ;
// glitch-soc additions
const sharedCWState = useSelector ( state => state . getIn ( [ 'state' , 'content_warnings' , 'shared_state' ] ) ) ;
const [ expanded , setExpanded ] = useState ( undefined ) ;
const parseClick = useCallback ( ( e , destination ) => {
2019-09-22 03:01:16 +09:00
if ( e . button === 0 && ! ( e . ctrlKey || e . altKey || e . metaKey ) ) {
if ( destination === undefined ) {
if ( unread ) {
2024-01-28 22:53:08 +09:00
dispatch ( markConversationRead ( id ) ) ;
2019-09-22 03:01:16 +09:00
}
destination = ` /statuses/ ${ lastStatus . get ( 'id' ) } ` ;
}
2023-10-20 02:44:55 +09:00
history . push ( destination ) ;
2019-09-22 03:01:16 +09:00
e . preventDefault ( ) ;
}
2024-01-28 22:53:08 +09:00
} , [ dispatch , history , unread , id , lastStatus ] ) ;
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
const handleMouseEnter = useCallback ( ( { currentTarget } ) => {
2021-02-01 05:25:31 +09:00
if ( autoPlayGif ) {
2019-10-02 00:11:14 +09:00
return ;
}
2021-02-01 05:25:31 +09:00
const emojis = currentTarget . querySelectorAll ( '.custom-emoji' ) ;
2019-10-02 00:11:14 +09:00
for ( var i = 0 ; i < emojis . length ; i ++ ) {
let emoji = emojis [ i ] ;
2021-02-01 05:25:31 +09:00
emoji . src = emoji . getAttribute ( 'data-original' ) ;
2019-10-02 00:11:14 +09:00
}
2024-01-28 22:53:08 +09:00
} , [ ] ) ;
2019-10-02 00:11:14 +09:00
2024-01-28 22:53:08 +09:00
const handleMouseLeave = useCallback ( ( { currentTarget } ) => {
2021-02-01 05:25:31 +09:00
if ( autoPlayGif ) {
return ;
}
2019-10-02 00:11:14 +09:00
2021-02-01 05:25:31 +09:00
const emojis = currentTarget . querySelectorAll ( '.custom-emoji' ) ;
2019-10-02 00:11:14 +09:00
2021-02-01 05:25:31 +09:00
for ( var i = 0 ; i < emojis . length ; i ++ ) {
let emoji = emojis [ i ] ;
emoji . src = emoji . getAttribute ( 'data-static' ) ;
}
2024-01-28 22:53:08 +09:00
} , [ ] ) ;
2019-06-09 19:07:23 +09:00
2024-01-28 22:53:08 +09:00
const handleClick = useCallback ( ( ) => {
2019-06-09 19:07:23 +09:00
if ( unread ) {
2024-01-28 22:53:08 +09:00
dispatch ( markConversationRead ( id ) ) ;
2019-06-09 19:07:23 +09:00
}
2024-01-28 22:53:08 +09:00
history . push ( ` /@ ${ lastStatus . getIn ( [ 'account' , 'acct' ] ) } / ${ lastStatus . get ( 'id' ) } ` ) ;
} , [ dispatch , history , unread , id , lastStatus ] ) ;
const handleMarkAsRead = useCallback ( ( ) => {
dispatch ( markConversationRead ( id ) ) ;
} , [ dispatch , id ] ) ;
const handleReply = useCallback ( ( ) => {
dispatch ( ( _ , getState ) => {
let state = getState ( ) ;
if ( state . getIn ( [ 'compose' , 'text' ] ) . trim ( ) . length !== 0 ) {
dispatch ( openModal ( {
modalType : 'CONFIRM' ,
modalProps : {
message : intl . formatMessage ( messages . replyMessage ) ,
confirm : intl . formatMessage ( messages . replyConfirm ) ,
onConfirm : ( ) => dispatch ( replyCompose ( lastStatus , history ) ) ,
} ,
} ) ) ;
} else {
dispatch ( replyCompose ( lastStatus , history ) ) ;
}
} ) ;
} , [ dispatch , lastStatus , history , intl ] ) ;
const handleDelete = useCallback ( ( ) => {
dispatch ( deleteConversation ( id ) ) ;
} , [ dispatch , id ] ) ;
const handleHotkeyMoveUp = useCallback ( ( ) => {
onMoveUp ( id ) ;
} , [ id , onMoveUp ] ) ;
const handleHotkeyMoveDown = useCallback ( ( ) => {
onMoveDown ( id ) ;
} , [ id , onMoveDown ] ) ;
const handleConversationMute = useCallback ( ( ) => {
if ( lastStatus . get ( 'muted' ) ) {
dispatch ( unmuteStatus ( lastStatus . get ( 'id' ) ) ) ;
} else {
dispatch ( muteStatus ( lastStatus . get ( 'id' ) ) ) ;
2019-09-22 03:01:16 +09:00
}
2024-01-28 22:53:08 +09:00
} , [ dispatch , lastStatus ] ) ;
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
const handleShowMore = useCallback ( ( ) => {
if ( lastStatus . get ( 'hidden' ) ) {
dispatch ( revealStatus ( lastStatus . get ( 'id' ) ) ) ;
} else {
dispatch ( hideStatus ( lastStatus . get ( 'id' ) ) ) ;
2019-06-09 19:07:23 +09:00
}
2024-01-28 22:53:08 +09:00
if ( lastStatus . get ( 'spoiler_text' ) ) {
setExpanded ( ! expanded ) ;
2019-09-22 03:01:16 +09:00
}
2024-01-28 22:53:08 +09:00
} , [ dispatch , lastStatus , expanded ] ) ;
2019-09-22 03:01:16 +09:00
2024-01-31 06:42:22 +09:00
if ( ! lastStatus ) {
return null ;
}
2024-01-28 22:53:08 +09:00
const menu = [
{ text : intl . formatMessage ( messages . open ) , action : handleClick } ,
null ,
{ text : intl . formatMessage ( lastStatus . get ( 'muted' ) ? messages . unmuteConversation : messages . muteConversation ) , action : handleConversationMute } ,
] ;
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
if ( unread ) {
menu . push ( { text : intl . formatMessage ( messages . markAsRead ) , action : handleMarkAsRead } ) ;
menu . push ( null ) ;
}
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
menu . push ( { text : intl . formatMessage ( messages . delete ) , action : handleDelete } ) ;
const names = accounts . map ( a => (
< Permalink to = { ` /@ ${ a . get ( 'acct' ) } ` } href = { a . get ( 'url' ) } key = { a . get ( 'id' ) } title = { a . get ( 'acct' ) } >
< bdi >
< strong
className = 'display-name__html'
dangerouslySetInnerHTML = { { _ _html : a . get ( 'display_name_html' ) } }
/ >
< / bdi >
< / Permalink >
) ) . reduce ( ( prev , cur ) => [ prev , ', ' , cur ] ) ;
const handlers = {
reply : handleReply ,
open : handleClick ,
moveUp : handleHotkeyMoveUp ,
moveDown : handleHotkeyMoveDown ,
toggleHidden : handleShowMore ,
} ;
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
let media = null ;
if ( lastStatus . get ( 'media_attachments' ) . size > 0 ) {
media = < AttachmentList compact media = { lastStatus . get ( 'media_attachments' ) } / > ;
}
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
return (
< HotKeys handlers = { handlers } >
< div className = { classNames ( 'conversation focusable muted' , { 'conversation--unread' : unread } ) } tabIndex = { 0 } >
< div className = 'conversation__avatar' onClick = { handleClick } role = 'presentation' >
< AvatarComposite accounts = { accounts } size = { 48 } / >
< / div >
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
< div className = 'conversation__content' >
< div className = 'conversation__content__info' >
< div className = 'conversation__content__relative-time' >
{ unread && < span className = 'conversation__unread' / > } < RelativeTimestamp timestamp = { lastStatus . get ( 'created_at' ) } / >
< / div >
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
< div className = 'conversation__content__names' onMouseEnter = { handleMouseEnter } onMouseLeave = { handleMouseLeave } >
< FormattedMessage id = 'conversation.with' defaultMessage = 'With {names}' values = { { names : < span > { names } < / span > } } / >
2019-09-22 03:01:16 +09:00
< / div >
2024-01-28 22:53:08 +09:00
< / div >
2019-09-22 03:01:16 +09:00
2024-01-28 22:53:08 +09:00
< StatusContent
status = { lastStatus }
parseClick = { parseClick }
expanded = { sharedCWState ? lastStatus . get ( 'hidden' ) : expanded }
onExpandedToggle = { handleShowMore }
collapsible
media = { media }
/ >
< div className = 'status__action-bar' >
< IconButton className = 'status__action-bar-button' title = { intl . formatMessage ( messages . reply ) } icon = 'reply' iconComponent = { ReplyIcon } onClick = { handleReply } / >
< div className = 'status__action-bar-dropdown' >
< DropdownMenuContainer
scrollKey = { scrollKey }
status = { lastStatus }
items = { menu }
icon = 'ellipsis-h'
iconComponent = { MoreHorizIcon }
size = { 18 }
direction = 'right'
title = { intl . formatMessage ( messages . more ) }
/ >
2019-09-22 03:01:16 +09:00
< / div >
< / div >
< / div >
2024-01-28 22:53:08 +09:00
< / div >
< / HotKeys >
) ;
} ;
Conversation . propTypes = {
conversation : ImmutablePropTypes . map . isRequired ,
scrollKey : PropTypes . string ,
onMoveUp : PropTypes . func ,
onMoveDown : PropTypes . func ,
} ;