Merge branch 'master' into fix-security-subscriptions

This commit is contained in:
Ulf Gebhardt 2023-07-20 17:11:20 +02:00 committed by GitHub
commit fbd2b08737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 304 additions and 133 deletions

View File

@ -8,6 +8,7 @@
# TODO change this to last message date
enum _RoomOrdering {
lastMessageAt_desc
createdAt_desc
}
type Room {

View File

@ -0,0 +1,67 @@
<template>
<div class="add-chat-room-by-user-search">
<ds-flex class="headline">
<h2 class="title">{{ $t('chat.addRoomHeadline') }}</h2>
<base-button class="close-button" icon="close" circle @click="closeUserSearch" />
</ds-flex>
<ds-space margin-bottom="small" />
<ds-space>
<select-user-search :id="id" ref="selectUserSearch" @select-user="selectUser" />
</ds-space>
</div>
</template>
<script>
import SelectUserSearch from '~/components/generic/SelectUserSearch/SelectUserSearch'
export default {
name: 'AddChatRoomByUserSearch',
components: {
SelectUserSearch,
},
props: {
// chatRooms: {
// type: Array,
// default: [],
// },
},
data() {
return {
id: 'search-user-to-add-to-group',
user: {},
}
},
methods: {
selectUser(user) {
this.user = user
// if (this.groupMembers.find((member) => member.id === this.user.id)) {
// this.$toast.error(this.$t('group.errors.userAlreadyMember', { name: this.user.name }))
// this.$refs.selectUserSearch.clear()
// return
// }
this.$refs.selectUserSearch.clear()
this.$emit('close-user-search')
this.addChatRoom(this.user?.id)
},
async addChatRoom(userId) {
this.$emit('add-chat-room', userId)
},
closeUserSearch() {
this.$emit('close-user-search')
},
},
}
</script>
<style lang="scss">
.add-chat-room-by-user-search {
background-color: white;
padding: $space-base;
}
.ds-flex.headline {
justify-content: space-between;
}
.ds-flex.headline .close-button {
margin-top: -6px;
}
</style>

View File

@ -19,12 +19,13 @@
show-audio="false"
:styles="JSON.stringify(computedChatStyle)"
:show-footer="true"
@send-message="sendMessage($event.detail[0])"
@fetch-messages="fetchMessages($event.detail[0])"
@fetch-more-rooms="fetchRooms"
:responsive-breakpoint="responsiveBreakpoint"
:single-room="singleRoom"
show-reaction-emojis="false"
@send-message="sendMessage($event.detail[0])"
@fetch-messages="fetchMessages($event.detail[0])"
@fetch-more-rooms="fetchRooms"
@add-room="toggleUserSearch"
@show-demo-options="showDemoOptions = $event"
>
<div
@ -172,22 +173,7 @@ export default {
},
mounted() {
if (this.singleRoom) {
this.$apollo
.mutate({
mutation: createRoom(),
variables: {
userId: this.roomId,
},
})
.then(({ data: { CreateRoom } }) => {
this.fetchRooms({ room: CreateRoom })
})
.catch((error) => {
this.$toast.error(error)
})
.finally(() => {
// this.loading = false
})
this.newRoom(this.roomId)
} else {
this.fetchRooms()
}
@ -247,9 +233,10 @@ export default {
commitUnreadRoomCount: 'chat/UPDATE_ROOM_COUNT',
commitRoomIdFromSingleRoom: 'chat/UPDATE_ROOM_ID',
}),
async fetchRooms({ room, options = {} } = {}) {
this.roomsLoaded = options.refetch ? this.roomsLoaded : false
const offset = (options.refetch ? 0 : this.roomPage) * this.roomPageSize
async fetchRooms({ room } = {}) {
this.roomsLoaded = false
const offset = this.roomPage * this.roomPageSize
try {
const {
data: { Room },
@ -267,17 +254,7 @@ export default {
const rmsIds = []
;[...Room, ...this.rooms].forEach((r) => {
if (!rmsIds.find((v) => v === r.id)) {
rms.push({
...r,
index: r.lastMessage?.date,
lastMessage: {
...r.lastMessage,
content: r.lastMessage?.content.trim().substring(0, 30),
},
users: r.users.map((u) => {
return { ...u, username: u.name, avatar: u.avatar?.url }
}),
})
rms.push(this.fixRoomObject(r))
rmsIds.push(r.id)
}
})
@ -398,6 +375,10 @@ export default {
const changedRoom = { ...this.rooms[roomIndex] }
changedRoom.lastMessage = createdMessage
changedRoom.lastMessage.content = changedRoom.lastMessage.content.trim().substring(0, 30)
// move current room to top (not 100% working)
// const rooms = [...this.rooms]
// rooms.splice(roomIndex,1)
// this.rooms = [changedRoom, ...rooms]
this.rooms[roomIndex] = changedRoom
} catch (error) {
this.$toast.error(error.message)
@ -412,6 +393,58 @@ export default {
if (!fullname) return
return fullname.match(/\b\w/g).join('').substring(0, 3).toUpperCase()
},
toggleUserSearch() {
this.$emit('toggle-user-search')
},
fixRoomObject(room) {
// This fixes the room object which arrives from the backend
const fixedRoom = {
...room,
index: room.lastMessage ? room.lastMessage.date : room.createdAt,
lastMessage: room.lastMessage
? {
...room.lastMessage,
content: room.lastMessage?.content?.trim().substring(0, 30),
}
: null,
users: room.users.map((u) => {
return { ...u, username: u.name, avatar: u.avatar?.url }
}),
}
if (!fixedRoom.avatar) {
// as long as we cannot query avatar on CreateRoom
fixedRoom.avatar = fixedRoom.users.find((u) => u.id !== this.currentUser.id).avatar
}
return fixedRoom
},
newRoom(userId) {
this.$apollo
.mutate({
mutation: createRoom(),
variables: {
userId,
},
})
.then(({ data: { CreateRoom } }) => {
const roomIndex = this.rooms.findIndex((r) => r.id === CreateRoom.roomId)
const room = this.fixRoomObject(CreateRoom)
if (roomIndex === -1) {
this.rooms = [room, ...this.rooms]
}
this.fetchMessages({ room, options: { refetch: true } })
this.$emit('show-chat', CreateRoom.id)
})
.catch((error) => {
this.$toast.error(error.message)
})
.finally(() => {
// this.loading = false
})
},
},
}
</script>

View File

@ -8,7 +8,6 @@
:disabled="disabled && !update"
@click="handleCancel"
data-test="cancel-button"
danger
>
{{ $t('actions.cancel') }}
</base-button>

View File

@ -169,12 +169,7 @@
</ds-flex-item>
<ds-flex-item width="0.15" />
<ds-flex-item class="action-buttons-group" width="2">
<base-button
data-test="cancel-button"
:disabled="loading"
@click="$router.back()"
danger
>
<base-button data-test="cancel-button" :disabled="loading" @click="$router.back()">
{{ $t('actions.cancel') }}
</base-button>
<base-button type="submit" icon="check" :loading="loading" :disabled="errors" filled>

View File

@ -3,33 +3,7 @@
<h2 class="title">{{ $t('group.addUser') }}</h2>
<ds-space margin-bottom="small" />
<ds-space>
<ds-select
type="search"
icon="search"
label-prop="id"
v-model="query"
:id="id"
:icon-right="null"
:options="users"
:loading="$apollo.queries.searchUsers.loading"
:filter="(item) => item"
:no-options-available="$t('group.addUserNoOptions')"
:auto-reset-search="true"
:placeholder="$t('group.addUserPlaceholder')"
@focus.capture.native="onFocus"
@input.native="handleInput"
@keyup.enter.native="onEnter"
@keyup.delete.native="onDelete"
@keyup.esc.native="clear"
@blur.capture.native="onBlur"
@input.exact="onSelect"
>
<template #option="{ option }">
<p>
<user-teaser :user="option" :showPopover="false" :linkToProfile="false" />
</p>
</template>
</ds-select>
<select-user-search :id="id" ref="selectUserSearch" @select-user="selectUser" />
<ds-modal
v-if="isOpen"
force
@ -49,16 +23,15 @@
</ds-space>
</div>
</template>
<script>
import { changeGroupMemberRoleMutation } from '~/graphql/groups.js'
import { searchUsers } from '~/graphql/Search.js'
import UserTeaser from '~/components/UserTeaser/UserTeaser.vue'
import { isEmpty } from 'lodash'
import SelectUserSearch from '~/components/generic/SelectUserSearch/SelectUserSearch'
export default {
name: 'AddGroupMember',
components: {
UserTeaser,
SelectUserSearch,
},
props: {
groupId: {
@ -72,62 +45,34 @@ export default {
},
data() {
return {
users: [],
id: 'search-user-to-add-to-group',
query: '',
user: {},
isOpen: false,
}
},
computed: {
startSearch() {
return this.query && this.query.length > 3
},
},
methods: {
cancelModal() {
this.clear()
this.$refs.selectUserSearch.clear()
this.isOpen = false
},
closeModal() {
this.clear()
this.$refs.selectUserSearch.clear()
this.isOpen = false
},
confirmModal() {
this.addMemberToGroup()
this.isOpen = false
this.clear()
this.$refs.selectUserSearch.clear()
},
onFocus() {},
onBlur() {
this.query = ''
},
handleInput(event) {
this.query = event.target ? event.target.value.trim() : ''
},
onDelete(event) {
const value = event.target ? event.target.value.trim() : ''
if (isEmpty(value)) {
this.clear()
} else {
this.handleInput(event)
}
},
clear() {
this.query = ''
this.user = {}
this.users = []
},
onSelect(item) {
this.user = item
selectUser(user) {
this.user = user
if (this.groupMembers.find((member) => member.id === this.user.id)) {
this.$toast.error(this.$t('group.errors.userAlreadyMember', { name: this.user.name }))
this.clear()
this.$refs.selectUserSearch.clear()
return
}
this.isOpen = true
},
onEnter() {},
async addMemberToGroup() {
const newRole = 'usual'
const username = this.user.name
@ -148,29 +93,9 @@ export default {
}
},
},
apollo: {
searchUsers: {
query() {
return searchUsers
},
variables() {
return {
query: this.query,
firstUsers: 5,
usersOffset: 0,
}
},
skip() {
return !this.startSearch
},
update({ searchUsers }) {
this.users = searchUsers.users
},
fetchPolicy: 'cache-and-network',
},
},
}
</script>
<style lang="scss">
.add-group-member {
background-color: white;

View File

@ -56,7 +56,7 @@
>
{{ isEditing ? $t('actions.save') : texts.addButton }}
</base-button>
<base-button v-if="isEditing" id="cancel" danger @click="handleCancel()">
<base-button v-if="isEditing" id="cancel" @click="handleCancel()">
{{ $t('actions.cancel') }}
</base-button>
</ds-space>

View File

@ -0,0 +1,109 @@
<template>
<ds-select
class="select-user-search"
type="search"
icon="search"
label-prop="id"
v-model="query"
:id="id"
:icon-right="null"
:options="users"
:loading="$apollo.queries.searchUsers.loading"
:filter="(item) => item"
:no-options-available="$t('group.addUserNoOptions')"
:auto-reset-search="true"
:placeholder="$t('group.addUserPlaceholder')"
@focus.capture.native="onFocus"
@input.native="handleInput"
@keyup.enter.native="onEnter"
@keyup.delete.native="onDelete"
@keyup.esc.native="clear"
@blur.capture.native="onBlur"
@input.exact="onSelect"
>
<template #option="{ option }">
<p>
<user-teaser :user="option" :showPopover="false" :linkToProfile="false" />
</p>
</template>
</ds-select>
</template>
<script>
import { isEmpty } from 'lodash'
import { searchUsers } from '~/graphql/Search.js'
import UserTeaser from '~/components/UserTeaser/UserTeaser.vue'
export default {
name: 'SelectUserSearch',
components: {
UserTeaser,
},
props: {
id: {
type: String,
required: true,
},
},
data() {
return {
users: [],
query: '',
user: {},
}
},
computed: {
startSearch() {
return this.query && this.query.length > 3
},
},
methods: {
onFocus() {},
onBlur() {
this.query = ''
},
handleInput(event) {
this.query = event.target ? event.target.value.trim() : ''
},
onDelete(event) {
const value = event.target ? event.target.value.trim() : ''
if (isEmpty(value)) {
this.clear()
} else {
this.handleInput(event)
}
},
clear() {
this.query = ''
this.user = {}
this.users = []
},
onSelect(item) {
this.user = item
this.$emit('select-user', this.user)
},
onEnter() {},
},
apollo: {
searchUsers: {
query() {
return searchUsers
},
variables() {
return {
query: this.query,
firstUsers: 5,
usersOffset: 0,
}
},
skip() {
return !this.startSearch
},
update({ searchUsers }) {
this.users = searchUsers.users
},
fetchPolicy: 'cache-and-network',
},
},
}
</script>

View File

@ -5,18 +5,32 @@ export const createRoom = () => gql`
CreateRoom(userId: $userId) {
id
roomId
roomName
lastMessageAt
createdAt
unreadCount
#avatar
users {
_id
id
name
avatar {
url
}
}
}
}
`
export const roomQuery = () => gql`
query Room($first: Int, $offset: Int, $id: ID) {
Room(first: $first, offset: $offset, id: $id, orderBy: lastMessageAt_desc) {
Room(first: $first, offset: $offset, id: $id, orderBy: [createdAt_desc, lastMessageAt_desc]) {
id
roomId
roomName
avatar
lastMessageAt
createdAt
unreadCount
lastMessage {
_id

View File

@ -78,6 +78,7 @@
}
},
"chat": {
"addRoomHeadline": "Suche Nutzer für neuen Chat",
"cancelSelectMessage": "Abbrechen",
"conversationStarted": "Unterhaltung startete am:",
"isOnline": "online",
@ -901,7 +902,7 @@
"Tag": "Hashtag ::: Hashtags",
"User": "Nutzer ::: Nutzer"
},
"hint": "Wonach suchst Du? Nutze !… für Beiträge, @… für Mitglieder, &… für Gruppen, #… für Hashtags",
"hint": "!... sucht Beiträge, @... sucht Nutzer, &... sucht Gruppen, #… sucht Hashtags",
"no-results": "Keine Ergebnisse für \"{search}\" gefunden. Versuch' es mit einem anderen Begriff!",
"page": "Seite",
"placeholder": "Suchen",

View File

@ -78,6 +78,7 @@
}
},
"chat": {
"addRoomHeadline": "Search User for new Chat",
"cancelSelectMessage": "Cancel",
"conversationStarted": "Conversation started on:",
"isOnline": "is online",
@ -475,7 +476,7 @@
"addMemberToGroupSuccess": "“{name}” was added to the group with the role “{role}”!",
"addUser": "Add User",
"addUserNoOptions": "No users found!",
"addUserPlaceholder": " Username",
"addUserPlaceholder": "User name",
"allGroups": "All Groups",
"button": {
"tooltip": "Show groups"
@ -901,7 +902,7 @@
"Tag": "Hashtag ::: Hashtags",
"User": "User ::: Users"
},
"hint": "What are you searching for? Use !… for posts, @… for users, &… for groups, #… for hashtags.",
"hint": "!... searches posts, @... searches users, &... searches groups, #… searches hashtags",
"no-results": "No results found for \"{search}\". Try a different search term!",
"page": "Page",
"placeholder": "Search",

View File

@ -1,16 +1,36 @@
<template>
<div>
<ds-heading tag="h1">{{ $t('chat.page.headline') }}</ds-heading>
<chat :roomId="getShowChat.showChat ? getShowChat.roomID : null" />
<add-chat-room-by-user-search
v-if="showUserSearch"
@add-chat-room="addChatRoom"
@close-user-search="showUserSearch = false"
/>
<ds-space margin-bottom="small" />
<chat
:roomId="getShowChat.showChat ? getShowChat.roomID : null"
ref="chat"
@toggle-user-search="showUserSearch = !showUserSearch"
:show-room="showRoom"
/>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import AddChatRoomByUserSearch from '~/components/Chat/AddChatRoomByUserSearch'
import Chat from '../components/Chat/Chat.vue'
export default {
components: { Chat },
components: {
AddChatRoomByUserSearch,
Chat,
},
data() {
return {
showUserSearch: false,
}
},
mounted() {
this.showChat({ showChat: false, roomID: null })
},
@ -23,6 +43,12 @@ export default {
...mapMutations({
showChat: 'chat/SET_OPEN_CHAT',
}),
addChatRoom(userID) {
this.$refs.chat.newRoom(userID)
},
showRoom(roomId) {
this.showChat({ showChat: true, roomID: roomId })
},
},
}
</script>