diff --git a/changelog.d/chat_vew.add b/changelog.d/chat_vew.add
new file mode 100644
index 000000000..7e649d454
--- /dev/null
+++ b/changelog.d/chat_vew.add
@@ -0,0 +1 @@
+Chat view for threads
diff --git a/src/App.js b/src/App.js
index 934b96713..4ce6f1abc 100644
--- a/src/App.js
+++ b/src/App.js
@@ -186,7 +186,11 @@ export default {
return useShoutStore().joined
},
isChats() {
- return this.$route.name === 'chat' || this.$route.name === 'chats'
+ return (
+ this.$route.name === 'chat' ||
+ this.$route.name === 'chats' ||
+ this.$route.name === 'conversation2'
+ )
},
isListEdit() {
return this.$route.name === 'lists-edit'
@@ -205,7 +209,7 @@ export default {
)
},
hideShoutbox() {
- return useMergedConfigStore().mergedConfig.hideShoutbox
+ return this.isChats || useMergedConfigStore().mergedConfig.hideShoutbox
},
reverseLayout() {
const { thirdColumnMode, sidebarRight: reverseSetting } =
diff --git a/src/api/chats.js b/src/api/chats.js
index 114038e52..5d766dec4 100644
--- a/src/api/chats.js
+++ b/src/api/chats.js
@@ -1,6 +1,9 @@
import { paramsString, promisedRequest } from './helpers.js'
-import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.service.js'
+import {
+ parseChat,
+ parseChatMessage,
+} from 'src/services/entity_normalizer/entity_normalizer.service.js'
const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}`
@@ -15,7 +18,7 @@ export const chats = ({ credentials }) =>
url: PLEROMA_CHATS_URL,
credentials,
}).then(({ data }) => ({
- chatList: data.map(parseChat).filter((c) => c),
+ data: data.map(parseChat).filter((c) => c),
}))
export const getOrCreateChat = ({ accountId, credentials }) =>
@@ -23,7 +26,7 @@ export const getOrCreateChat = ({ accountId, credentials }) =>
url: PLEROMA_CHAT_URL(accountId),
method: 'POST',
credentials,
- })
+ }).then(({ data }) => ({ data: parseChat(data) }))
export const chatMessages = ({
id,
@@ -36,7 +39,9 @@ export const chatMessages = ({
url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }),
method: 'GET',
credentials,
- })
+ }).then(({ data }) => ({
+ data: data.map(parseChatMessage).filter((c) => c),
+ }))
}
export const sendChatMessage = ({
@@ -66,7 +71,9 @@ export const sendChatMessage = ({
payload,
credentials,
headers,
- })
+ }).then(({ data }) => ({
+ data: parseChatMessage(data),
+ }))
}
export const readChat = ({ id, lastReadId, credentials }) =>
diff --git a/src/api/user.js b/src/api/user.js
index 17e13195d..9a55a6b93 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -207,7 +207,7 @@ export const postStatus = ({
idempotencyKey,
}) => {
const form = new FormData()
- const pollOptions = poll.options || []
+ const pollOptions = poll?.options || []
form.append('status', status)
form.append('source', 'Pleroma FE')
@@ -266,7 +266,7 @@ export const editStatus = ({
contentType,
}) => {
const form = new FormData()
- const pollOptions = poll.options || []
+ const pollOptions = poll?.options || []
form.append('status', status)
if (spoilerText) form.append('spoiler_text', spoilerText)
diff --git a/src/boot/routes.js b/src/boot/routes.js
index d50baab04..5897fad92 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -1,5 +1,3 @@
-import { defineAsyncComponent } from 'vue'
-
import AuthForm from 'src/components/auth_form/auth_form.js'
import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue'
import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue'
@@ -65,6 +63,14 @@ export default (store) => {
component: ConversationPage,
meta: { dontScroll: true },
},
+ {
+ name: 'conversation2',
+ path: '/conversation/:statusId',
+ component: () => import('src/components/chat_view/chat_view.vue'),
+ props: true,
+ meta: { dontScroll: true },
+ beforeEnter: validateAuthenticatedRoute,
+ },
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{
name: 'remote-user-profile-acct',
@@ -81,23 +87,18 @@ export default (store) => {
{
name: 'external-user-profile',
path: '/users/$:id',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile.vue'),
- ),
+ component: () => import('src/components/user_profile/user_profile.vue'),
},
{
name: 'user-profile-admin-view',
path: '/users/$:id/admin_view',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile_admin_view.vue'),
- ),
+ component: () =>
+ import('src/components/user_profile/user_profile_admin_view.vue'),
},
{
name: 'interactions',
path: '/users/:username/interactions',
- component: defineAsyncComponent(
- () => import('src/components/interactions/interactions.vue'),
- ),
+ component: () => import('src/components/interactions/interactions.vue'),
beforeEnter: validateAuthenticatedRoute,
},
{
@@ -109,39 +110,31 @@ export default (store) => {
{
name: 'registration',
path: '/registration',
- component: defineAsyncComponent(
- () => import('src/components/registration/registration.vue'),
- ),
+ component: () => import('src/components/registration/registration.vue'),
},
{
name: 'password-reset',
path: '/password-reset',
- component: defineAsyncComponent(
- () => import('src/components/password_reset/password_reset.vue'),
- ),
+ component: () =>
+ import('src/components/password_reset/password_reset.vue'),
props: true,
},
{
name: 'registration-token',
path: '/registration/:token',
- component: defineAsyncComponent(
- () => import('src/components/registration/registration.vue'),
- ),
+ component: () => import('src/components/registration/registration.vue'),
},
{
name: 'friend-requests',
path: '/friend-requests',
- component: defineAsyncComponent(
- () => import('src/components/follow_requests/follow_requests.vue'),
- ),
+ component: () =>
+ import('src/components/follow_requests/follow_requests.vue'),
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'notifications',
path: '/:username/notifications',
- component: defineAsyncComponent(
- () => import('src/components/notifications/notifications.vue'),
- ),
+ component: () => import('src/components/notifications/notifications.vue'),
props: () => ({ disableTeleport: true }),
beforeEnter: validateAuthenticatedRoute,
},
@@ -153,98 +146,74 @@ export default (store) => {
{
name: 'shout-panel',
path: '/shout-panel',
- component: defineAsyncComponent(
- () => import('src/components/shout_panel/shout_panel.vue'),
- ),
+ component: () => import('src/components/shout_panel/shout_panel.vue'),
props: () => ({ floating: false }),
},
{
name: 'oauth-callback',
path: '/oauth-callback',
- component: defineAsyncComponent(
- () => import('src/components/oauth_callback/oauth_callback.vue'),
- ),
+ component: () =>
+ import('src/components/oauth_callback/oauth_callback.vue'),
props: (route) => ({ code: route.query.code }),
},
{
name: 'search',
path: '/search',
- component: defineAsyncComponent(
- () => import('src/components/search/search.vue'),
- ),
+ component: () => import('src/components/search/search.vue'),
props: (route) => ({ query: route.query.query }),
},
{
name: 'who-to-follow',
path: '/who-to-follow',
- component: defineAsyncComponent(
- () => import('src/components/who_to_follow/who_to_follow.vue'),
- ),
+ component: () => import('src/components/who_to_follow/who_to_follow.vue'),
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'about',
path: '/about',
- component: defineAsyncComponent(
- () => import('src/components/about/about.vue'),
- ),
+ component: () => import('src/components/about/about.vue'),
},
{
name: 'announcements',
path: '/announcements',
- component: defineAsyncComponent(
- () =>
- import('src/components/announcements_page/announcements_page.vue'),
- ),
+ component: () =>
+ import('src/components/announcements_page/announcements_page.vue'),
},
{
name: 'drafts',
path: '/drafts',
- component: defineAsyncComponent(
- () => import('src/components/drafts/drafts.vue'),
- ),
+ component: () => import('src/components/drafts/drafts.vue'),
},
{
name: 'user-profile',
path: '/users/:name',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile.vue'),
- ),
+ component: () => import('src/components/user_profile/user_profile.vue'),
},
{
name: 'legacy-user-profile',
path: '/:name',
- component: defineAsyncComponent(
- () => import('src/components/user_profile/user_profile.vue'),
- ),
+ component: () => import('src/components/user_profile/user_profile.vue'),
},
{
name: 'lists',
path: '/lists',
- component: defineAsyncComponent(
- () => import('src/components/lists/lists.vue'),
- ),
+ component: () => import('src/components/lists/lists.vue'),
},
{
name: 'lists-timeline',
path: '/lists/:id',
- component: defineAsyncComponent(
- () => import('src/components/lists_timeline/lists_timeline.vue'),
- ),
+ component: () =>
+ import('src/components/lists_timeline/lists_timeline.vue'),
},
{
name: 'lists-edit',
path: '/lists/:id/edit',
- component: defineAsyncComponent(
- () => import('src/components/lists_edit/lists_edit.vue'),
- ),
+ component: () => import('src/components/lists_edit/lists_edit.vue'),
},
{
name: 'lists-new',
path: '/lists/new',
- component: defineAsyncComponent(
- () => import('src/components/lists_edit/lists_edit.vue'),
- ),
+ component: () => import('src/components/lists_edit/lists_edit.vue'),
},
{
name: 'edit-navigation',
@@ -256,19 +225,14 @@ export default (store) => {
{
name: 'bookmark-folders',
path: '/bookmark_folders',
- component: defineAsyncComponent(
- () => import('src/components/bookmark_folders/bookmark_folders.vue'),
- ),
+ component: () =>
+ import('src/components/bookmark_folders/bookmark_folders.vue'),
},
{
name: 'bookmark-folder-new',
path: '/bookmarks/new-folder',
- component: defineAsyncComponent(
- () =>
- import(
- 'src/components/bookmark_folder_edit/bookmark_folder_edit.vue'
- ),
- ),
+ component: () =>
+ import('src/components/bookmark_folder_edit/bookmark_folder_edit.vue'),
},
{
name: 'bookmark-folder',
@@ -278,12 +242,8 @@ export default (store) => {
{
name: 'bookmark-folder-edit',
path: '/bookmarks/:id/edit',
- component: defineAsyncComponent(
- () =>
- import(
- 'src/components/bookmark_folder_edit/bookmark_folder_edit.vue'
- ),
- ),
+ component: () =>
+ import('src/components/bookmark_folder_edit/bookmark_folder_edit.vue'),
},
]
@@ -291,19 +251,16 @@ export default (store) => {
routes = routes.concat([
{
name: 'chat',
- path: '/users/:username/chats/:recipient_id',
- component: defineAsyncComponent(
- () => import('src/components/chat/chat.vue'),
- ),
+ path: '/users/:username/chats/:chatUserId',
+ component: () => import('src/components/chat_view/chat_view.vue'),
meta: { dontScroll: false },
+ props: true,
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'chats',
path: '/users/:username/chats',
- component: defineAsyncComponent(
- () => import('src/components/chat_list/chat_list.vue'),
- ),
+ component: () => import('src/components/chat_list/chat_list.vue'),
meta: { dontScroll: false },
beforeEnter: validateAuthenticatedRoute,
},
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
deleted file mode 100644
index ca7a025ec..000000000
--- a/src/components/chat/chat.js
+++ /dev/null
@@ -1,433 +0,0 @@
-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 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'
-import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
-import {
- getNewTopPosition,
- getScrollPosition,
- isBottomedOut,
- isScrollable,
-} from './chat_layout_utils.js'
-
-import { useInterfaceStore } from 'src/stores/interface.js'
-import { useMergedConfigStore } from 'src/stores/merged_config.js'
-import { useOAuthStore } from 'src/stores/oauth.js'
-
-import {
- chatMessages,
- getOrCreateChat,
- sendChatMessage,
-} from 'src/api/chats.js'
-import { WSConnectionStatus } from 'src/api/websocket.js'
-
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
-
-library.add(faChevronDown, faChevronLeft)
-
-const BOTTOMED_OUT_OFFSET = 10
-const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
-const SAFE_RESIZE_TIME_OFFSET = 100
-const MARK_AS_READ_DELAY = 1500
-const MAX_RETRIES = 10
-
-const Chat = {
- components: {
- ChatMessage,
- ChatTitle,
- PostStatusForm,
- },
- data() {
- return {
- jumpToBottomButtonVisible: false,
- hoveredMessageChainId: undefined,
- lastScrollPosition: {},
- scrollableContainerHeight: '100%',
- errorLoadingChat: false,
- messageRetriers: {},
- }
- },
- created() {
- this.startFetching()
- window.addEventListener('resize', this.handleResize)
- },
- mounted() {
- window.addEventListener('scroll', this.handleScroll)
- if (typeof document.hidden !== 'undefined') {
- document.addEventListener(
- 'visibilitychange',
- this.handleVisibilityChange,
- false,
- )
- }
-
- this.$nextTick(() => {
- this.handleResize()
- })
- },
- unmounted() {
- window.removeEventListener('scroll', this.handleScroll)
- window.removeEventListener('resize', this.handleResize)
- if (typeof document.hidden !== 'undefined')
- document.removeEventListener(
- 'visibilitychange',
- this.handleVisibilityChange,
- false,
- )
- this.$store.dispatch('clearCurrentChat')
- },
- computed: {
- recipient() {
- return this.currentChat && this.currentChat.account
- },
- recipientId() {
- return this.$route.params.recipient_id
- },
- formPlaceholder() {
- if (this.recipient) {
- return this.$t('chats.message_user', {
- nickname: this.recipient.screen_name_ui,
- })
- } else {
- return ''
- }
- },
- chatViewItems() {
- return chatService.getView(this.currentChatMessageService)
- },
- newMessageCount() {
- return (
- this.currentChatMessageService &&
- this.currentChatMessageService.newMessageCount
- )
- },
- streamingEnabled() {
- return (
- this.mergedConfig.useStreamingApi &&
- this.mastoUserSocketStatus === WSConnectionStatus.JOINED
- )
- },
- ...mapGetters([
- 'currentChat',
- 'currentChatMessageService',
- 'findOpenedChatByRecipientId',
- ]),
- ...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
- ...mapPiniaState(useInterfaceStore, {
- mobileLayout: (store) => store.layoutType === 'mobile',
- }),
- ...mapState({
- mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
- currentUser: (state) => state.users.currentUser,
- }),
- },
- watch: {
- chatViewItems() {
- // 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)
- this.$nextTick(() => {
- if (bottomedOutBeforeUpdate) {
- this.scrollDown()
- }
- })
- },
- $route: function () {
- this.startFetching()
- },
- mastoUserSocketStatus(newValue) {
- if (newValue === WSConnectionStatus.JOINED) {
- this.fetchChat({ isFirstFetch: true })
- }
- },
- },
- methods: {
- // Used to animate the avatar near the first message of the message chain when any message belonging to the chain is hovered
- onMessageHover({ isHovered, messageChainId }) {
- this.hoveredMessageChainId = isHovered ? messageChainId : undefined
- },
- onFilesDropped() {
- this.$nextTick(() => {
- this.handleResize()
- })
- },
- handleVisibilityChange() {
- this.$nextTick(() => {
- if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
- this.scrollDown({ forceRead: true })
- }
- })
- },
- // "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
- handleResize(opts = {}) {
- const { delayed = false } = opts
-
- if (delayed) {
- setTimeout(() => {
- this.handleResize({ ...opts, delayed: false })
- }, SAFE_RESIZE_TIME_OFFSET)
- return
- }
-
- this.$nextTick(() => {
- const { offsetHeight = undefined } = getScrollPosition()
- const diff = offsetHeight - this.lastScrollPosition.offsetHeight
- if (diff !== 0 && !this.bottomedOut()) {
- this.$nextTick(() => {
- window.scrollBy({ top: -Math.trunc(diff) })
- })
- }
- this.lastScrollPosition = getScrollPosition()
- })
- },
- scrollDown(options = {}) {
- const { behavior = 'auto', forceRead = false } = options
- this.$nextTick(() => {
- window.scrollTo({
- top: document.documentElement.scrollHeight,
- behavior,
- })
- })
- if (forceRead) {
- this.readChat()
- }
- },
- readChat() {
- if (
- !(
- this.currentChatMessageService && this.currentChatMessageService.maxId
- )
- ) {
- return
- }
- if (document.hidden) {
- return
- }
- const lastReadId = this.currentChatMessageService.maxId
- this.$store.dispatch('readChat', {
- id: this.currentChat.id,
- lastReadId,
- })
- },
- bottomedOut(offset) {
- return isBottomedOut(offset)
- },
- reachedTop() {
- return window.scrollY <= 0
- },
- cullOlderCheck() {
- window.setTimeout(() => {
- if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
- this.$store.dispatch(
- 'cullOlderMessages',
- this.currentChatMessageService.chatId,
- )
- }
- }, 5000)
- },
- handleScroll: throttle(function () {
- this.lastScrollPosition = getScrollPosition()
- if (!this.currentChat) {
- return
- }
-
- if (this.reachedTop()) {
- this.fetchChat({ maxId: this.currentChatMessageService.minId })
- } else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
- this.jumpToBottomButtonVisible = false
- this.cullOlderCheck()
- if (this.newMessageCount > 0) {
- // Use a delay before marking as read to prevent situation where new messages
- // arrive just as you're leaving the view and messages that you didn't actually
- // get to see get marked as read.
- window.setTimeout(() => {
- // Don't mark as read if the element doesn't exist, user has left chat view
- if (this.$el) this.readChat()
- }, MARK_AS_READ_DELAY)
- }
- } else {
- this.jumpToBottomButtonVisible = true
- }
- }, 200),
- handleScrollUp(positionBeforeLoading) {
- const positionAfterLoading = getScrollPosition()
- window.scrollTo({
- top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
- })
- },
- fetchChat({ isFirstFetch = false, fetchLatest = false, maxId }) {
- const chatMessageService = this.currentChatMessageService
- if (!chatMessageService) {
- return
- }
- if (fetchLatest && this.streamingEnabled) {
- return
- }
-
- const chatId = chatMessageService.chatId
- const fetchOlderMessages = !!maxId
- const sinceId = fetchLatest && chatMessageService.maxId
-
- return chatMessages({
- id: chatId,
- maxId,
- sinceId,
- credentials: useOAuthStore().token,
- }).then(({ data: messages }) => {
- // Clear the current chat in case we're recovering from a ws connection loss.
- if (isFirstFetch) {
- chatService.clear(chatMessageService)
- }
-
- const positionBeforeUpdate = getScrollPosition()
- this.$store
- .dispatch('addChatMessages', { chatId, messages })
- .then(() => {
- this.$nextTick(() => {
- if (fetchOlderMessages) {
- this.handleScrollUp(positionBeforeUpdate)
- }
-
- // In vertical screens, the first batch of fetched messages may not always take the
- // full height of the scrollable container.
- // If this is the case, we want to fetch the messages until the scrollable container
- // is fully populated so that the user has the ability to scroll up and load the history.
- if (!isScrollable() && messages.length > 0) {
- this.fetchChat({
- maxId: this.currentChatMessageService.minId,
- })
- }
- })
- })
- })
- },
- async startFetching() {
- let chat = this.findOpenedChatByRecipientId(this.recipientId)
- if (!chat) {
- try {
- const { data } = await getOrCreateChat({
- accountId: this.recipientId,
- credentials: useOAuthStore().token,
- })
- chat = data
- } catch (e) {
- console.error('Error creating or getting a chat', e)
- this.errorLoadingChat = true
- }
- }
- if (chat) {
- this.$nextTick(() => {
- this.scrollDown({ forceRead: true })
- })
- this.$store.dispatch('addOpenedChat', { chat })
- this.doStartFetching()
- }
- },
- doStartFetching() {
- this.$store.dispatch('startFetchingCurrentChat', {
- fetcher: () =>
- promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000),
- })
- this.fetchChat({ isFirstFetch: true })
- },
- handleAttachmentPosting() {
- this.$nextTick(() => {
- this.handleResize()
- // When the posting form size changes because of a media attachment, we need an extra resize
- // to account for the potential delay in the DOM update.
- this.scrollDown({ forceRead: true })
- })
- },
- sendMessage({ status, media, idempotencyKey }) {
- const params = {
- id: this.currentChat.id,
- content: status,
- idempotencyKey,
- }
-
- if (media[0]) {
- params.mediaId = media[0].id
- }
-
- const fakeMessage = buildFakeMessage({
- attachments: media,
- chatId: this.currentChat.id,
- content: status,
- userId: this.currentUser.id,
- idempotencyKey,
- })
-
- this.$store
- .dispatch('addChatMessages', {
- chatId: this.currentChat.id,
- messages: [fakeMessage],
- })
- .then(() => {
- this.handleAttachmentPosting()
- })
-
- return this.doSendMessage({
- params,
- fakeMessage,
- retriesLeft: MAX_RETRIES,
- })
- },
- doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
- if (retriesLeft <= 0) return
-
- sendChatMessage({
- ...params,
- credentials: useOAuthStore().token,
- })
- .then(({ data }) => {
- this.$store.dispatch('addChatMessages', {
- chatId: this.currentChat.id,
- updateMaxId: false,
- messages: [{ ...data, fakeId: fakeMessage.id }],
- })
-
- return data
- })
- .catch((error) => {
- console.error('Error sending message', error)
- this.$store.dispatch('handleMessageError', {
- chatId: this.currentChat.id,
- fakeId: fakeMessage.id,
- isRetry: retriesLeft !== MAX_RETRIES,
- })
- if (
- (error.statusCode >= 500 && error.statusCode < 600) ||
- error.message === 'Failed to fetch'
- ) {
- this.messageRetriers[fakeMessage.id] = setTimeout(
- () => {
- this.doSendMessage({
- params,
- fakeMessage,
- retriesLeft: retriesLeft - 1,
- })
- },
- 1000 * 2 ** (MAX_RETRIES - retriesLeft),
- )
- }
- return {}
- })
-
- return Promise.resolve(fakeMessage)
- },
- goBack() {
- this.$router.push({
- name: 'chats',
- params: { username: this.currentUser.screen_name },
- })
- },
- },
-}
-
-export default Chat
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
deleted file mode 100644
index cedbdce69..000000000
--- a/src/components/chat/chat.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-
-