basic view chat routing and component structure

This commit is contained in:
Mary Kate 2020-09-09 17:20:10 -05:00 committed by Angelina Filippova
parent c46793cfaa
commit 410ae72b9e
9 changed files with 693 additions and 12 deletions

32
src/api/chat.js Normal file
View file

@ -0,0 +1,32 @@
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function deleteChatMessage(id, message_id, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/chats/{id}/messages/${message_id}`,
method: 'delete',
headers: authHeaders(token)
})
}
export async function fetchChat(id, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/chats/${id}`,
method: 'get',
headers: authHeaders(token)
})
}
export async function fetchChatMessages(id, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/chats/${id}/messages`,
method: 'get',
headers: authHeaders(token)
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}

View file

@ -0,0 +1,163 @@
<template>
<el-card v-if="!message.deleted" class="message-card" @click.native="handleRouteChange()">
<div slot="header">
<div class="message-header">
{{ message.created_at }}
</div>
</div>
<div class="message-body">
{{ message.content }}
</div>
</el-card>
</template>
<script>
export default {
name: 'ChatMessage',
props: {
message: {
type: Object,
required: true
},
page: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
}
},
methods: {
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.chat-card {
margin-bottom: 10px;
cursor: pointer;
.account {
line-height: 26px;
font-size: 13px;
color: #606266;
}
.account:hover {
text-decoration: underline;
}
.deactivated {
color: gray;
line-height: 28px;
vertical-align: middle;
}
.image {
width: 20%;
img {
width: 100%;
}
}
.router-link {
text-decoration: none;
}
.show-more-button {
margin-left: 5px;
}
.chat-account {
display: flex;
align-items: center;
}
.chat-avatar-img {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 5px;
}
.chat-account-name {
display: inline-block;
margin: 0;
font-size: 15px;
font-weight: 500;
}
.chat-body {
display: flex;
flex-direction: column;
}
.chat-card-header {
display: flex;
align-items: center;
}
.chat-checkbox {
margin-right: 7px;
}
.chat-content {
font-size: 15px;
line-height: 26px;
}
.chat-created-at {
font-size: 13px;
color: #606266;
}
.chat-deleted {
font-style: italic;
margin-top: 3px;
}
.chat-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-tags {
display: inline;
}
.chat-without-content {
font-style: italic;
}
}
@media only screen and (max-width:480px) {
.el-message {
min-width: 80%;
}
.el-message-box {
width: 80%;
}
.chat-card {
.el-card__header {
padding: 10px 17px;
}
.el-tag {
margin: 3px 0;
}
.chat-account-container {
margin-bottom: 5px;
}
.chat-actions-button {
margin: 3px 0 3px;
}
.chat-actions {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.chat-footer {
flex-direction: column;
align-items: flex-start;
margin-top: 10px;
}
.chat-header {
display: flex;
flex-direction: column;
align-items: flex-start;
}
}
}
</style>

View file

@ -67,6 +67,7 @@ export default {
reports: 'Reports', reports: 'Reports',
invites: 'Invites', invites: 'Invites',
statuses: 'Statuses', statuses: 'Statuses',
chats: 'Chats',
settings: 'Settings', settings: 'Settings',
moderationLog: 'Moderation Log', moderationLog: 'Moderation Log',
mediaProxyCache: 'MediaProxy Cache', mediaProxyCache: 'MediaProxy Cache',
@ -299,6 +300,11 @@ export default {
unlisted: 'Unlisted', unlisted: 'Unlisted',
openStatusInInstance: 'Open status in instance' openStatusInInstance: 'Open status in instance'
}, },
chats: {
chats: 'Chats',
loadMore: 'Load more',
chatHistory: 'Chat History'
},
userProfile: { userProfile: {
tags: 'Tags', tags: 'Tags',
moderator: 'Moderator', moderator: 'Moderator',

View file

@ -35,6 +35,21 @@ const statuses = {
] ]
} }
const chatsDisabled = disabledFeatures.includes('chats')
const chats = {
path: '/chats',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/chats/index'),
name: 'Chats',
meta: { title: 'chats', icon: 'form', noCache: true }
}
],
hidden: true
}
const reportsDisabled = disabledFeatures.includes('reports') const reportsDisabled = disabledFeatures.includes('reports')
const reports = { const reports = {
path: '/reports', path: '/reports',
@ -169,6 +184,7 @@ export const asyncRouterMap = [
] ]
}, },
...(statusesDisabled ? [] : [statuses]), ...(statusesDisabled ? [] : [statuses]),
...(chatsDisabled ? [] : [chats]),
...(reportsDisabled ? [] : [reports]), ...(reportsDisabled ? [] : [reports]),
...(invitesDisabled ? [] : [invites]), ...(invitesDisabled ? [] : [invites]),
...(emojiPacksDisabled ? [] : [emojiPacks]), ...(emojiPacksDisabled ? [] : [emojiPacks]),
@ -211,5 +227,17 @@ export const asyncRouterMap = [
], ],
hidden: true hidden: true
}, },
{
path: '/chats/:id',
component: Layout,
children: [
{
path: '',
name: 'ChatsShow',
component: () => import('@/views/chats/show')
}
],
hidden: true
},
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]

View file

@ -13,6 +13,7 @@ import relays from './modules/relays'
import reports from './modules/reports' import reports from './modules/reports'
import settings from './modules/settings' import settings from './modules/settings'
import status from './modules/status' import status from './modules/status'
import chat from './modules/chat'
import tagsView from './modules/tagsView' import tagsView from './modules/tagsView'
import user from './modules/user' import user from './modules/user'
import userProfile from './modules/userProfile' import userProfile from './modules/userProfile'
@ -34,6 +35,7 @@ const store = new Vuex.Store({
reports, reports,
settings, settings,
status, status,
chat,
tagsView, tagsView,
user, user,
userProfile, userProfile,

39
src/store/modules/chat.js Normal file
View file

@ -0,0 +1,39 @@
import { fetchChat, fetchChatMessages } from '@/api/chat'
const chat = {
state: {
fetchedChat: {},
fetchedChatMessages: {},
loading: false,
chatAuthor: {}
},
mutations: {
SET_LOADING: (state, status) => {
state.loading = status
},
SET_CHAT: (state, chat) => {
state.fetchedChat = chat
},
SET_CHAT_MESSAGES: (state, chatMessages) => {
state.fetchedChatMessages = chatMessages
}
},
actions: {
async FetchChat({ commit, dispatch, getters, state }, id) {
commit('SET_LOADING', true)
const chat = await fetchChat(id, getters.authHost, getters.token)
commit('SET_CHAT', chat.data)
commit('SET_LOADING', false)
},
async FetchChatMessages({ commit, dispatch, getters, state }, id) {
commit('SET_LOADING', true)
const chat = await fetchChatMessages(id, getters.authHost, getters.token)
commit('SET_CHAT_MESSAGES', chat.data)
commit('SET_LOADING', false)
}
}
}
export default chat

167
src/views/chats/index.vue Normal file
View file

@ -0,0 +1,167 @@
<template>
<div v-if="!loadingPeers" class="chats-container">
<div class="chats-header">
<h1>
{{ $t('chats.chats') }}
</h1>
<reboot-button/>
</div>
<div class="chats-header-container"/>
</div>
</template>
<script>
import RebootButton from '@/components/RebootButton'
export default {
name: 'Chats',
components: {
RebootButton
},
data() {
return {
}
},
computed: {
allLoaded() {
return this.$store.state.chat.chatsByInstance.allLoaded
},
buttonLoading() {
return this.$store.state.chat.chatsByInstance.buttonLoading
},
currentInstance() {
return this.selectedInstance === this.$store.state.user.authHost
},
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
isTablet() {
return this.$store.state.app.device === 'tablet'
},
page() {
return this.$store.state.chat.chatsByInstance.page
},
pageSize() {
return this.$store.state.chat.chatsByInstance.pageSize
}
},
mounted() {
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('NeedReboot')
},
destroyed() {
this.clearSelection()
this.$store.dispatch('ClearState')
},
methods: {
clearSelection() {
this.selectedUsers = []
},
handleLoadMore() {
this.$store.dispatch('HandlePageChange', this.page + 1)
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.chats-container {
padding: 0 15px;
h1 {
margin: 10px 0 15px 0;
}
.chat-container {
margin: 0 0 10px;
}
}
.chats-header-container {
.el-button.is-plain:focus, .el-button.is-plain:hover {
border-color: #DCDFE6;
color: #606266;
cursor: default
}
}
.checkbox-container {
margin-bottom: 15px;
}
.filter-container {
display: flex;
height: 36px;
justify-content: space-between;
align-items: center;
margin: 22px 0 15px 0;
}
.reboot-button {
padding: 10px;
margin: 0;
width: 145px;
}
.select-instance {
width: 396px;
}
.chats-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.chats-header-container {
display: flex;
align-items: center;
justify-content: space-between;
}
.chats-pagination {
padding: 15px 0;
text-align: center;
}
@media only screen and (max-width:480px) {
.checkbox-container {
margin-bottom: 10px;
}
.filter-container {
display: flex;
height: 36px;
flex-direction: column;
margin: 10px 0;
}
.select-field {
width: 100%;
margin-bottom: 5px;
}
.select-instance {
width: 100%;
}
.chats-header-container {
flex-direction: column;
align-items: flex-start;
.el-button-group {
width: 100%;
}
.el-button {
padding: 10px 6.5px;
width: 50%;
}
.el-button-group>.el-button:first-child {
border-bottom-left-radius: 0;
}
.el-button-group>.el-button:not(:first-child):not(:last-child).private-button {
border-top-right-radius: 4px;
}
.el-button-group>.el-button:not(:first-child):not(:last-child).public-button {
border-bottom-left-radius: 4px;
border-top: white;
}
.el-button-group>.el-button:last-child {
border-top-right-radius: 0;
border-top: white;
}
.reboot-button {
margin: 10px 0 0 0;
}
}
}
</style>

242
src/views/chats/show.vue Normal file
View file

@ -0,0 +1,242 @@
<template>
<div v-if="!loading" class="chat-show-container">
<header v-if="isDesktop || isTablet" class="user-page-header">
<div class="avatar-name-container"/>
</header>
<div v-if="isMobile" class="chat-page-header-container">
<header class="user-page-header">
<div class="avatar-name-container"/>
<reboot-button/>
</header>
</div>
<div class="chat-container">
<div class="chat-card-header">
<h1>
{{ $t('chats.chatHistory') }}
</h1>
<!-- <img v-if="propertyExists(chat.account, 'avatar')" :src="chat.account.avatar" class="chat-avatar-img">
<span v-if="propertyExists(chat.account, 'username')" class="chat-account-name">{{ chat.account.username }}</span>
<span v-else>
<span v-if="propertyExists(chat.account, 'username')" class="chat-account-name">
{{ chat.account.username }}
</span>
<span v-else class="chat-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
</span> -->
</div>
<div class="chat-messages">
<div v-for="message in chatMessages" :key="message.id" class="">
<chat-message :message="message"/>
</div>
</div>
</div>
</div>
</template>
<script>
import ChatMessage from '@/components/ChatMessage'
import RebootButton from '@/components/RebootButton'
export default {
name: 'ChatShow',
components: { RebootButton, ChatMessage },
data() {
return {
}
},
computed: {
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
isTablet() {
return this.$store.state.app.device === 'tablet'
},
loading() {
return this.$store.state.chat.loading
},
chat() {
return this.$store.state.chat.fetchedChat
},
chatMessages() {
return this.$store.state.chat.fetchedChatMessages
}
},
beforeMount: function() {
this.$store.dispatch('NeedReboot')
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('FetchChat', this.$route.params.id)
this.$store.dispatch('FetchChatMessages', this.$route.params.id)
},
methods: {
propertyExists(account, property) {
return account[property]
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.avatar-name-container {
display: flex;
align-items: center;
.el-icon-top-right {
font-size: 2em;
line-height: 36px;
color: #606266;
}
}
.avatar-name-header {
display: flex;
height: 40px;
align-items: center;
}
.invalid {
color: gray;
}
.no-chats {
margin-left: 28px;
color: #606266;
}
.password-reset-token {
margin: 0 0 14px 0;
}
.password-reset-token-dialog {
width: 50%
}
.reboot-button {
padding: 10px;
margin-left: 6px;
}
.recent-chats-container-show {
display: flex;
flex-direction: column;
.el-timeline-item {
margin-left: 20px;
}
.recent-chats {
margin-left: 20px;
}
.show-private-chats {
margin-left: 20px;
margin-bottom: 20px;
}
}
.reset-password-link {
text-decoration: underline;
}
.router-link {
text-decoration: none;
}
.chat-container {
margin: 0 15px 0 20px;
}
.chats {
padding: 0 20px 0 0;
}
.user-page-header {
display: flex;
justify-content: space-between;
margin: 22px 15px 22px 20px;
padding: 0;
align-items: center;
h1 {
display: inline;
margin: 0 0 0 10px;
}
}
@media only screen and (min-width: 1824px) {
.chat-show-container {
max-width: 1824px;
margin: auto;
}
}
@media only screen and (max-width:480px) {
.avatar-name-container {
margin-bottom: 10px;
}
.el-timeline-item__wrapper {
padding-left: 18px;
}
.left-header-container {
align-items: center;
display: flex;
justify-content: space-between;
}
.password-reset-token-dialog {
width: 85%
}
.recent-chats {
margin: 20px 10px 15px 10px;
}
.recent-chats-container-show {
width: 100%;
margin: 0 0 0 10px;
.el-timeline-item {
margin-left: 0;
}
.recent-chats {
margin-left: 0;
}
.show-private-chats {
margin: 0 10px 20px 0;
}
}
.chat-card {
.el-card__body {
padding: 15px;
}
}
.chat-container {
margin: 0 10px;
}
.chats {
padding-right: 10px;
margin-left: 0;
.el-timeline-item__wrapper {
margin-right: 10px;
}
}
.user-page-header {
padding: 0;
margin: 7px 15px 5px 10px;
}
.chat-page-header-container {
width: 100%;
.el-dropdown {
width: stretch;
margin: 0 10px 15px 10px;
}
}
}
@media only screen and (max-width:801px) and (min-width: 481px) {
.recent-chats-container-show {
width: 97%;
margin: 0 20px;
.el-timeline-item {
margin-left: 2px;
}
.recent-chats {
margin: 20px 10px 15px 0;
}
.show-private-chats {
margin: 0 10px 20px 0;
}
}
.show-private-chats {
margin: 0 10px 20px 0;
}
.user-page-header {
padding: 0;
margin: 7px 15px 20px 20px;
}
}
</style>

View file

@ -120,6 +120,7 @@
</tr> </tr>
<tr v-for="chat in chats" :key="chat.id" class="el-table__row chat-item"> <tr v-for="chat in chats" :key="chat.id" class="el-table__row chat-item">
<td> <td>
<a v-if="propertyExists(chat, 'id')" :href="`/#/chats/${chat.id}/`">
<div class="chat-card-header"> <div class="chat-card-header">
<img v-if="propertyExists(chat.account, 'avatar')" :src="chat.account.avatar" class="chat-avatar-img"> <img v-if="propertyExists(chat.account, 'avatar')" :src="chat.account.avatar" class="chat-avatar-img">
<span v-if="propertyExists(chat.account, 'username')" class="chat-account-name">{{ chat.account.username }}</span> <span v-if="propertyExists(chat.account, 'username')" class="chat-account-name">{{ chat.account.username }}</span>
@ -133,6 +134,7 @@
<div class="chat-card-preview"> <div class="chat-card-preview">
<span v-if="propertyExists(chat, 'last_message')" class="chat-preview">{{ chat.last_message.content }}</span> <span v-if="propertyExists(chat, 'last_message')" class="chat-preview">{{ chat.last_message.content }}</span>
</div> </div>
</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>