Merge branch 'master' into 3086-avoid-red-color-for-non-essential-things--new

This commit is contained in:
Hannes Heine 2023-07-20 10:45:31 +02:00 committed by GitHub
commit d13f6f0728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 139 additions and 60 deletions

View File

@ -9,10 +9,9 @@ function walkRecursive(data, fields, fieldName, callback, _key?) {
if (!Array.isArray(fields)) {
throw new Error('please provide an fields array for the walkRecursive helper')
}
if (data && typeof data === 'string' && fields.includes(_key)) {
// well we found what we searched for, lets replace the value with our callback result
const key = _key.split('!')
if (key.length === 1 || key[1] !== fieldName) data = callback(data, key[0])
const fieldDef = fields.find((f) => f.field === _key)
if (data && typeof data === 'string' && fieldDef) {
if (!fieldDef.excludes?.includes(fieldName)) data = callback(data, _key)
} else if (data && Array.isArray(data)) {
// go into the rabbit hole and dig through that array
data.forEach((res, index) => {

View File

@ -30,6 +30,7 @@ const standardSanitizeHtmlOptions = {
'strike',
'span',
'blockquote',
'usertag',
],
allowedAttributes: {
a: ['href', 'class', 'target', 'data-*', 'contenteditable'],

View File

@ -3,11 +3,11 @@ import { cleanHtml } from '../middleware/helpers/cleanHtml'
// exclamation mark separetes field names, that should not be sanitized
const fields = [
'content',
'contentExcerpt',
'reasonDescription',
'description!embed',
'descriptionExcerpt',
{ field: 'content', excludes: ['CreateMessage', 'Message'] },
{ field: 'contentExcerpt' },
{ field: 'reasonDescription' },
{ field: 'description', excludes: ['embed'] },
{ field: 'descriptionExcerpt' },
]
export default {

View File

@ -81,7 +81,7 @@ export default {
createdAt: toString(datetime()),
id: apoc.create.uuid(),
indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END,
content: $content,
content: LEFT($content,2000),
saved: true,
distributed: false,
seen: false

View File

@ -4,7 +4,7 @@
<vue-advanced-chat
:theme="theme"
:current-user-id="currentUser.id"
:room-id="!singleRoom ? roomId : null"
:room-id="computedRoomId"
:template-actions="JSON.stringify(templatesText)"
:menu-actions="JSON.stringify(menuActions)"
:text-messages="JSON.stringify(textMessages)"
@ -27,15 +27,33 @@
show-reaction-emojis="false"
@show-demo-options="showDemoOptions = $event"
>
<div slot="menu-icon" @click.prevent.stop="$emit('close-single-room', true)">
<div v-if="singleRoom">
<ds-icon name="close"></ds-icon>
</div>
<div
v-if="selectedRoom && selectedRoom.roomId"
slot="room-options"
class="chat-room-options"
>
<ds-flex v-if="singleRoom">
<ds-flex-item centered class="single-chat-bubble">
<nuxt-link :to="{ name: 'chat' }">
<base-icon name="chat-bubble" />
</nuxt-link>
</ds-flex-item>
<ds-flex-item centered>
<div
class="vac-svg-button vac-room-options"
@click="$emit('close-single-room', true)"
>
<slot name="menu-icon">
<ds-icon name="close" />
</slot>
</div>
</ds-flex-item>
</ds-flex>
</div>
<div slot="room-header-avatar">
<div
v-if="selectedRoom && selectedRoom.avatar && selectedRoom.avatar !== 'default-avatar'"
v-if="selectedRoom && selectedRoom.avatar"
class="vac-avatar"
:style="{ 'background-image': `url('${selectedRoom.avatar}')` }"
/>
@ -46,7 +64,7 @@
<div v-for="room in rooms" :slot="'room-list-avatar_' + room.id" :key="room.id">
<div
v-if="room.avatar && room.avatar !== 'default-avatar'"
v-if="room.avatar"
class="vac-avatar"
:style="{ 'background-image': `url('${room.avatar}')` }"
/>
@ -89,11 +107,6 @@ export default {
data() {
return {
menuActions: [
// NOTE: if menuActions is empty, the related slot is not shown
{
name: 'dummyItem',
title: 'Just a dummy item',
},
/*
{
name: 'inviteUser',
@ -197,10 +210,24 @@ export default {
computed: {
...mapGetters({
currentUser: 'auth/user',
getStoreRoomId: 'chat/roomID',
}),
computedChatStyle() {
return chatStyle.STYLE.light
},
computedRoomId() {
let roomId = null
if (!this.singleRoom) {
roomId = this.roomId
if (this.getStoreRoomId.roomId) {
roomId = this.getStoreRoomId.roomId
}
}
return roomId
},
textMessages() {
return {
ROOMS_EMPTY: this.$t('chat.roomsEmpty'),
@ -221,10 +248,11 @@ export default {
methods: {
...mapMutations({
commitUnreadRoomCount: 'chat/UPDATE_ROOM_COUNT',
commitRoomIdFromSingleRoom: 'chat/UPDATE_ROOM_ID',
}),
async fetchRooms({ room } = {}) {
this.roomsLoaded = false
const offset = this.roomPage * this.roomPageSize
async fetchRooms({ room, options = {} } = {}) {
this.roomsLoaded = options.refetch ? this.roomsLoaded : false
const offset = (options.refetch ? 0 : this.roomPage) * this.roomPageSize
try {
const {
data: { Room },
@ -238,21 +266,37 @@ export default {
fetchPolicy: 'no-cache',
})
const newRooms = Room.map((r) => {
return {
...r,
users: r.users.map((u) => {
return { ...u, username: u.name, avatar: u.avatar?.url }
}),
const rms = []
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 }
}),
})
rmsIds.push(r.id)
}
})
this.rooms = [...this.rooms, ...newRooms]
this.rooms = rms
if (Room.length < this.roomPageSize) {
this.roomsLoaded = true
}
this.roomPage += 1
if (this.singleRoom && this.rooms.length > 0) {
this.commitRoomIdFromSingleRoom(this.rooms[0].roomId)
} else if (this.getStoreRoomId.roomId) {
// reset store room id
this.commitRoomIdFromSingleRoom(null)
}
} catch (error) {
this.rooms = []
this.$toast.error(error.message)
@ -282,8 +326,14 @@ export default {
fetchPolicy: 'no-cache',
})
const newMsgIds = Message.filter((m) => m.seen === false).map((m) => m.id)
const newMsgIds = Message.filter(
(m) => m.seen === false && m.senderId !== this.currentUser.id,
).map((m) => m.id)
if (newMsgIds.length) {
const roomIndex = this.rooms.findIndex((r) => r.id === room.id)
const changedRoom = { ...this.rooms[roomIndex] }
changedRoom.unreadCount = changedRoom.unreadCount - newMsgIds.length
this.rooms[roomIndex] = changedRoom
this.$apollo
.mutate({
mutation: markMessagesAsSeen(),
@ -322,32 +372,36 @@ export default {
},
async chatMessageAdded({ data }) {
const roomIndex = this.rooms.findIndex((r) => r.id === data.chatMessageAdded.room.id)
const changedRoom = { ...this.rooms[roomIndex] }
changedRoom.lastMessage = data.chatMessageAdded
changedRoom.lastMessage.content = changedRoom.lastMessage.content.trim().substring(0, 30)
changedRoom.lastMessageAt = data.chatMessageAdded.date
changedRoom.unreadCount++
this.rooms[roomIndex] = changedRoom
if (data.chatMessageAdded.room.id === this.selectedRoom?.id) {
this.fetchMessages({ room: this.selectedRoom, options: { refetch: true } })
} else {
// TODO this might be optimized selectively (first page vs rest)
this.rooms = []
this.roomPage = 0
this.roomsLoaded = false
this.fetchRooms()
this.fetchRooms({ options: { refetch: true } })
}
},
async sendMessage(message) {
// check for usersTag and change userid to username
message.usersTag.forEach((userTag) => {
const needle = `<usertag>${userTag.id}</usertag>`
const replacement = `<usertag>@${userTag.name.replaceAll(' ', '-').toLowerCase()}</usertag>`
message.content = message.content.replaceAll(needle, replacement)
})
try {
await this.$apollo.mutate({
const {
data: { CreateMessage: createdMessage },
} = await this.$apollo.mutate({
mutation: createMessageMutation(),
variables: {
roomId: message.roomId,
content: message.content,
},
})
const roomIndex = this.rooms.findIndex((r) => r.id === message.roomId)
const changedRoom = { ...this.rooms[roomIndex] }
changedRoom.lastMessage = createdMessage
changedRoom.lastMessage.content = changedRoom.lastMessage.content.trim().substring(0, 30)
this.rooms[roomIndex] = changedRoom
} catch (error) {
this.$toast.error(error.message)
}
@ -389,4 +443,8 @@ body {
transform: translate(-50%, -50%);
}
}
.ds-flex-item.single-chat-bubble {
margin-right: 1em;
}
</style>

View File

@ -58,14 +58,14 @@ export default {
routes.push({
label: this.$t('group.contentMenu.visitGroupPage'),
icon: 'home',
name: 'group-id-slug',
path: `/groups/${this.group.id}`,
params: { id: this.group.id, slug: this.group.slug },
})
}
if (this.group.myRole === 'owner') {
routes.push({
label: this.$t('admin.settings.name'),
path: `/group/edit/${this.group.id}`,
path: `/groups/edit/${this.group.id}`,
icon: 'edit',
})
}

View File

@ -1,7 +1,7 @@
<template>
<nuxt-link
class="group-teaser"
:to="{ name: 'group-id-slug', params: { id: group.id, slug: group.slug } }"
:to="{ name: 'groups-id-slug', params: { id: group.id, slug: group.slug } }"
>
<base-card
:class="{

View File

@ -9,7 +9,7 @@
<p class="description">{{ $t(`notifications.reason.${notification.reason}`) }}</p>
<nuxt-link
class="link"
:to="{ name: isGroup ? 'group-id-slug' : 'post-id-slug', params, ...hashParam }"
:to="{ name: isGroup ? 'groups-id-slug' : 'post-id-slug', params, ...hashParam }"
@click.native="$emit('read')"
>
<base-card wideContent>

View File

@ -65,7 +65,7 @@
class="notification-mention-post"
:class="{ 'notification-status': notification.read }"
:to="{
name: isGroup(notification.from) ? 'group-id-slug' : 'post-id-slug',
name: isGroup(notification.from) ? 'groups-id-slug' : 'post-id-slug',
params: params(notification.from),
hash: hashParam(notification.from),
}"

View File

@ -96,7 +96,7 @@ export default {
groupLink() {
const { id, slug } = this.group
if (!(id && slug)) return ''
return { name: 'group-id-slug', params: { slug, id } }
return { name: 'groups-id-slug', params: { slug, id } }
},
groupSlug() {
const { slug } = this.group || {}

View File

@ -148,7 +148,7 @@ export default {
case 'User':
return 'profile-id-slug'
case 'Group':
return 'group-id-slug'
return 'groups-id-slug'
default:
return null
}

View File

@ -4,8 +4,23 @@ export const createMessageMutation = () => {
return gql`
mutation ($roomId: ID!, $content: String!) {
CreateMessage(roomId: $roomId, content: $content) {
#_id
id
indexId
content
senderId
author {
id
}
username
avatar
date
room {
id
}
saved
distributed
seen
}
}
`
@ -26,6 +41,9 @@ export const messageQuery = () => {
username
avatar
date
room {
id
}
saved
distributed
seen

View File

@ -24,7 +24,7 @@ const options = {
}
`,
message: 'error-pages.group-not-found',
path: 'group',
path: 'groups',
}
const persistentLinks = PersistentLinks(options)

View File

@ -59,7 +59,7 @@ export default {
})
this.$toast.success(this.$t('group.groupCreated'))
this.$router.history.push({
name: 'group-id-slug',
name: 'groups-id-slug',
params: { id: responseId, slug: responseSlug },
})
} catch (error) {

View File

@ -33,11 +33,11 @@ export default {
return [
{
name: this.$t('group.general'),
path: `/group/edit/${this.group.id}`,
path: `/groups/edit/${this.group.id}`,
},
{
name: this.$t('group.members'),
path: `/group/edit/${this.group.id}/members`,
path: `/groups/edit/${this.group.id}/members`,
},
]
},

View File

@ -60,7 +60,7 @@ export default {
})
this.$toast.success(this.$t('group.updatedGroup'))
this.$router.history.push({
name: 'group-id-slug',
name: 'groups-id-slug',
params: { id: responseId, slug: responseSlug },
})
} catch (error) {

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils'
import groups from './groups.vue'
import groups from './index.vue'
const localVue = global.localVue

View File

@ -7,7 +7,7 @@
<ds-space>
<!-- create group -->
<ds-space centered>
<nuxt-link :to="{ name: 'group-create' }">
<nuxt-link :to="{ name: 'groups-create' }">
<base-button
class="group-add-button"
icon="plus"

View File

@ -14,6 +14,9 @@ export const mutations = {
UPDATE_ROOM_COUNT(state, count) {
state.unreadRoomCount = count
},
UPDATE_ROOM_ID(state, roomid) {
state.roomId = roomid || null
},
}
export const getters = {