refactored chat into separate chat_view and chat_message_list
This commit is contained in:
parent
cab0e9a881
commit
55917125a8
12 changed files with 127 additions and 96 deletions
|
|
@ -294,7 +294,7 @@ export default (store) => {
|
|||
name: 'chat',
|
||||
path: '/users/:username/chats/:recipient_id',
|
||||
component: defineAsyncComponent(
|
||||
() => import('src/components/chat/chat.vue'),
|
||||
() => import('src/components/chat_view/chat_view.vue'),
|
||||
),
|
||||
meta: { dontScroll: false },
|
||||
beforeEnter: validateAuthenticatedRoute,
|
||||
|
|
|
|||
|
|
@ -22,10 +22,9 @@ library.add(faTimes, faEllipsisH)
|
|||
const ChatMessage = {
|
||||
name: 'ChatMessage',
|
||||
props: [
|
||||
'author',
|
||||
'edited',
|
||||
'noHeading',
|
||||
'chatViewItem',
|
||||
'chatItem',
|
||||
'hoveredMessageChain',
|
||||
],
|
||||
emits: ['hover'],
|
||||
|
|
@ -42,21 +41,26 @@ const ChatMessage = {
|
|||
computed: {
|
||||
// Returns HH:MM (hours and minutes) in local time.
|
||||
createdAt() {
|
||||
const time = this.chatViewItem.data.created_at
|
||||
const time = this.chatItem.data.created_at
|
||||
return time.toLocaleTimeString('en', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
})
|
||||
},
|
||||
author() {
|
||||
return this.$store.getters.findUser(
|
||||
this.chatItem.data.account_id
|
||||
)
|
||||
},
|
||||
isCurrentUser() {
|
||||
return this.message.account_id === this.currentUser.id
|
||||
},
|
||||
message() {
|
||||
return this.chatViewItem.data
|
||||
return this.chatItem.data
|
||||
},
|
||||
isMessage() {
|
||||
return this.chatViewItem.type === 'message'
|
||||
return this.chatItem.type === 'message'
|
||||
},
|
||||
messageForStatusContent() {
|
||||
return {
|
||||
|
|
@ -96,15 +100,15 @@ const ChatMessage = {
|
|||
onHover(bool) {
|
||||
this.$emit('hover', {
|
||||
isHovered: bool,
|
||||
messageChainId: this.chatViewItem.messageChainId,
|
||||
messageChainId: this.chatItem.messageChainId,
|
||||
})
|
||||
},
|
||||
async deleteMessage() {
|
||||
const confirmed = window.confirm(this.$t('chats.delete_confirm'))
|
||||
if (confirmed) {
|
||||
await this.$store.dispatch('deleteChatMessage', {
|
||||
messageId: this.chatViewItem.data.id,
|
||||
chatId: this.chatViewItem.data.chat_id,
|
||||
messageId: this.chatItem.data.id,
|
||||
chatId: this.chatItem.data.chat_id,
|
||||
})
|
||||
}
|
||||
this.hovered = false
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
class="avatar-wrapper"
|
||||
>
|
||||
<UserPopover
|
||||
v-if="chatViewItem.isHead"
|
||||
v-if="chatItem.isHead"
|
||||
:user-id="author.id"
|
||||
>
|
||||
<UserAvatar
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
>
|
||||
<div
|
||||
class="media status"
|
||||
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
|
||||
:class="{ 'without-attachment': !hasAttachment, 'pending': chatItem.data.pending, 'error': chatItem.data.error }"
|
||||
style="position: relative;"
|
||||
@mouseenter="hovered = true"
|
||||
@mouseleave="hovered = false"
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
v-else
|
||||
class="chat-message-date-separator"
|
||||
>
|
||||
<ChatMessageDate :date="chatViewItem.date" />
|
||||
<ChatMessageDate :date="chatItem.date" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
76
src/components/chat_message_list/chat_message_list.js
Normal file
76
src/components/chat_message_list/chat_message_list.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { throttle, orderBy, uniqueId } from 'lodash'
|
||||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
||||
import ChatMessage from 'src/components/chat_message/chat_message.vue'
|
||||
|
||||
const ChatMessageList = {
|
||||
components: {
|
||||
ChatMessage,
|
||||
},
|
||||
props: {
|
||||
messages: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hoveredMessageChainId: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
chatItems() {
|
||||
const messages = orderBy(this.messages, ['pending', 'id'], ['asc', 'asc'])
|
||||
return messages.reduceRight((acc, message, index) => {
|
||||
const date = new Date(message.created_at)
|
||||
|
||||
const olderMessage = messages[index - 1]
|
||||
const newerMessage = messages[index + 1]
|
||||
const newerItem = acc[acc.length - 1]
|
||||
|
||||
const diff = message.created_at - (olderMessage?.created_at || 0)
|
||||
const MAX_DIFF = 1000 * 60 // 5 minutes
|
||||
|
||||
const chatItem = {
|
||||
type: 'message',
|
||||
data: message,
|
||||
date,
|
||||
id: message.id,
|
||||
isTail: true,
|
||||
isHead: true,
|
||||
}
|
||||
|
||||
if (newerItem == null) {
|
||||
chatItem.messageChainId = uniqueId()
|
||||
} else {
|
||||
if (newerItem.type === 'date') {
|
||||
chatItem.messageChainId = uniqueId()
|
||||
} else if (newerItem.type === 'message') {
|
||||
if (newerItem.data.account_id !== message.account_id) {
|
||||
chatItem.messageChainId = uniqueId()
|
||||
} else {
|
||||
chatItem.messageChainId = newerItem.messageChainId
|
||||
chatItem.isTail = false
|
||||
newerItem.isHead = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff > MAX_DIFF || !olderMessage) {
|
||||
return [...acc, chatItem, {
|
||||
type: 'date',
|
||||
date,
|
||||
id: date.getTime().toString(),
|
||||
}]
|
||||
} else {
|
||||
return [...acc, chatItem]
|
||||
}
|
||||
}, []).reverse()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMessageHover({ isHovered, messageChainId }) {
|
||||
this.hoveredMessageChainId = isHovered ? messageChainId : undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatMessageList
|
||||
7
src/components/chat_message_list/chat_message_list.scss
Normal file
7
src/components/chat_message_list/chat_message_list.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.ChatMessageList {
|
||||
padding: 0 0.8em;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
}
|
||||
14
src/components/chat_message_list/chat_message_list.vue
Normal file
14
src/components/chat_message_list/chat_message_list.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div class="ChatMessageList">
|
||||
<ChatMessage
|
||||
v-for="chatItem in chatItems"
|
||||
:key="chatItem.id"
|
||||
:chat-item="chatItem"
|
||||
:hovered-message-chain="chatItem.messageChainId === hoveredMessageChainId"
|
||||
@hover="onMessageHover"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./chat_message_list.js"></script>
|
||||
<style src="./chat_message_list.scss" lang="scss" />
|
||||
|
|
@ -2,8 +2,8 @@ import { throttle } from 'lodash'
|
|||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
||||
import ChatMessage from 'src/components/chat_message/chat_message.vue'
|
||||
import ChatTitle from 'src/components/chat_title/chat_title.vue'
|
||||
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
|
||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||
import chatService from '../../services/chat_service/chat_service.js'
|
||||
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
||||
import {
|
||||
chatMessages,
|
||||
|
|
@ -38,10 +39,13 @@ const MAX_RETRIES = 10
|
|||
|
||||
const Chat = {
|
||||
components: {
|
||||
ChatMessage,
|
||||
ChatMessageList,
|
||||
ChatTitle,
|
||||
PostStatusForm,
|
||||
},
|
||||
props: {
|
||||
messages: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
jumpToBottomButtonVisible: false,
|
||||
|
|
@ -97,12 +101,11 @@ const Chat = {
|
|||
return ''
|
||||
}
|
||||
},
|
||||
chatViewItems() {
|
||||
return chatService.getView(this.currentChatMessageService?.messages)
|
||||
chatMessages() {
|
||||
return this.currentChatMessageService?.messages
|
||||
},
|
||||
newMessageCount() {
|
||||
return this.currentChatMessageService?.newMessageCount
|
||||
|
||||
},
|
||||
streamingEnabled() {
|
||||
return (
|
||||
|
|
@ -114,18 +117,18 @@ const Chat = {
|
|||
'currentChat',
|
||||
'currentChatMessageService',
|
||||
'findOpenedChatByRecipientId',
|
||||
'mergedConfig',
|
||||
]),
|
||||
...mapPiniaState(useInterfaceStore, {
|
||||
mobileLayout: (store) => store.layoutType === 'mobile',
|
||||
}),
|
||||
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
|
||||
...mapState({
|
||||
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
|
||||
currentUser: (state) => state.users.currentUser,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
chatViewItems() {
|
||||
chatMessages() {
|
||||
// We don't want to scroll to the bottom on a new message when the user is viewing older messages.
|
||||
// Therefore we need to know whether the scroll position was at the bottom before the DOM update.
|
||||
const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
name: 'Chat',
|
||||
selector: '.chat-message-list',
|
||||
selector: '.ChatMessageList',
|
||||
validInnerComponents: ['Text', 'Link', 'Icon', 'Avatar', 'ChatMessage'],
|
||||
defaultRules: [
|
||||
{
|
||||
|
|
@ -25,29 +25,7 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="chat-message-list message-list"
|
||||
:style="{ height: scrollableContainerHeight }"
|
||||
>
|
||||
<template v-if="!errorLoadingChat">
|
||||
<ChatMessage
|
||||
v-for="chatViewItem in chatViewItems"
|
||||
:key="chatViewItem.id"
|
||||
:author="recipient"
|
||||
:chat-view-item="chatViewItem"
|
||||
:hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId"
|
||||
@hover="onMessageHover"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else
|
||||
class="chat-loading-error"
|
||||
>
|
||||
<div class="alert error">
|
||||
{{ $t('chats.error_loading_chat') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChatMessageList :messages="chatMessages" />
|
||||
<div
|
||||
ref="footer"
|
||||
class="panel-body footer"
|
||||
|
|
@ -95,5 +73,5 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./chat.js"></script>
|
||||
<style src="./chat.scss" lang="scss" />
|
||||
<script src="./chat_view.js"></script>
|
||||
<style src="./chat_view.scss" lang="scss" />
|
||||
|
|
@ -159,60 +159,9 @@ const resetNewMessageCount = (storage) => {
|
|||
storage.lastSeenMessageId = storage.maxId
|
||||
}
|
||||
|
||||
// Inserts date separators and marks the head and tail if it's the chain of messages made by the same user
|
||||
const getView = (items = []) => {
|
||||
const messages = orderBy(items, ['pending', 'id'], ['asc', 'asc'])
|
||||
return messages.reduceRight((acc, message, index) => {
|
||||
const date = new Date(message.created_at)
|
||||
|
||||
const olderMessage = messages[index - 1]
|
||||
const newerMessage = messages[index + 1]
|
||||
const newerItem = acc[acc.length - 1]
|
||||
|
||||
const diff = message.created_at - (olderMessage?.created_at || 0)
|
||||
const MAX_DIFF = 1000 * 60 // 5 minutes
|
||||
|
||||
const chatItem = {
|
||||
type: 'message',
|
||||
data: message,
|
||||
date,
|
||||
id: message.id,
|
||||
isTail: true,
|
||||
isHead: true,
|
||||
}
|
||||
|
||||
if (newerItem == null) {
|
||||
chatItem.messageChainId = uniqueId()
|
||||
} else {
|
||||
if (newerItem.type === 'date') {
|
||||
chatItem.messageChainId = uniqueId()
|
||||
} else if (newerItem.type === 'message') {
|
||||
if (newerItem.data.account_id !== message.account_id) {
|
||||
chatItem.messageChainId = uniqueId()
|
||||
} else {
|
||||
chatItem.messageChainId = newerItem.messageChainId
|
||||
chatItem.isTail = false
|
||||
newerItem.isHead = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff > MAX_DIFF || !olderMessage) {
|
||||
return [...acc, chatItem, {
|
||||
type: 'date',
|
||||
date,
|
||||
id: date.getTime().toString(),
|
||||
}]
|
||||
} else {
|
||||
return [...acc, chatItem]
|
||||
}
|
||||
}, []).reverse()
|
||||
}
|
||||
|
||||
const ChatService = {
|
||||
add,
|
||||
empty,
|
||||
getView,
|
||||
deleteMessage,
|
||||
cullOlderMessages,
|
||||
resetNewMessageCount,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue