Merge branch 'master' into deployment-stage-ocelot-is-branded

This commit is contained in:
Ulf Gebhardt 2023-03-20 13:39:04 +01:00 committed by GitHub
commit 7e158b5136
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 560 additions and 66 deletions

View File

@ -51,6 +51,50 @@ const publishNotifications = async (context, promises) => {
})
}
const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => {
const { groupId, userId } = args
const user = await resolve(root, args, context, resolveInfo)
if (user) {
await publishNotifications(context, [
notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context),
])
}
return user
}
const handleLeaveGroup = async (resolve, root, args, context, resolveInfo) => {
const { groupId, userId } = args
const user = await resolve(root, args, context, resolveInfo)
if (user) {
await publishNotifications(context, [
notifyOwnersOfGroup(groupId, userId, 'user_left_group', context),
])
}
return user
}
const handleChangeGroupMemberRole = async (resolve, root, args, context, resolveInfo) => {
const { groupId, userId } = args
const user = await resolve(root, args, context, resolveInfo)
if (user) {
await publishNotifications(context, [
notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context),
])
}
return user
}
const handleRemoveUserFromGroup = async (resolve, root, args, context, resolveInfo) => {
const { groupId, userId } = args
const user = await resolve(root, args, context, resolveInfo)
if (user) {
await publishNotifications(context, [
notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context),
])
}
return user
}
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
const idsOfUsers = extractMentionedUsers(args.content)
const post = await resolve(root, args, context, resolveInfo)
@ -94,6 +138,72 @@ const postAuthorOfComment = async (commentId, { context }) => {
}
}
const notifyOwnersOfGroup = async (groupId, userId, reason, context) => {
const cypher = `
MATCH (group:Group { id: $groupId })<-[membership:MEMBER_OF]-(owner:User)
WHERE membership.role = 'owner'
WITH owner, group
MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(owner)
WITH group, owner, notification
SET notification.read = FALSE
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
SET notification.updatedAt = toString(datetime())
SET notification.relatedUserId = $userId
RETURN notification {.*, from: group, to: properties(owner)}
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
groupId,
reason,
userId,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
const notifications = await writeTxResultPromise
return notifications
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
}
const notifyMemberOfGroup = async (groupId, userId, reason, context) => {
const { user: owner } = context
const cypher = `
MATCH (user:User { id: $userId })
MATCH (group:Group { id: $groupId })
WITH user, group
MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(user)
WITH group, user, notification
SET notification.read = FALSE
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
SET notification.updatedAt = toString(datetime())
SET notification.relatedUserId = $ownerId
RETURN notification {.*, from: group, to: properties(user)}
`
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const notificationTransactionResponse = await transaction.run(cypher, {
groupId,
reason,
userId,
ownerId: owner.id,
})
return notificationTransactionResponse.records.map((record) => record.get('notification'))
})
try {
const notifications = await writeTxResultPromise
return notifications
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
}
const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
if (!(idsOfUsers && idsOfUsers.length)) return []
await validateNotifyUsers(label, reason)
@ -188,5 +298,9 @@ export default {
UpdatePost: handleContentDataOfPost,
CreateComment: handleContentDataOfComment,
UpdateComment: handleContentDataOfComment,
JoinGroup: handleJoinGroup,
LeaveGroup: handleLeaveGroup,
ChangeGroupMemberRole: handleChangeGroupMemberRole,
RemoveUserFromGroup: handleRemoveUserFromGroup,
},
}

View File

@ -3,6 +3,13 @@ import { cleanDatabase } from '../../db/factories'
import { createTestClient } from 'apollo-server-testing'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer, { pubsub } from '../../server'
import {
createGroupMutation,
joinGroupMutation,
leaveGroupMutation,
changeGroupMemberRoleMutation,
removeUserFromGroupMutation,
} from '../../graphql/groups'
let server, query, mutate, notifiedUser, authenticatedUser
let publishSpy
@ -92,6 +99,9 @@ describe('notifications', () => {
read
reason
createdAt
relatedUser {
id
}
from {
__typename
... on Post {
@ -102,6 +112,9 @@ describe('notifications', () => {
id
content
}
... on Group {
id
}
}
}
}
@ -185,6 +198,7 @@ describe('notifications', () => {
id: 'c47',
content: commentContent,
},
relatedUser: null,
},
],
},
@ -357,6 +371,7 @@ describe('notifications', () => {
id: 'p47',
content: expectedUpdatedContent,
},
relatedUser: null,
},
],
},
@ -513,6 +528,7 @@ describe('notifications', () => {
id: 'c47',
content: commentContent,
},
relatedUser: null,
},
],
},
@ -547,6 +563,7 @@ describe('notifications', () => {
id: 'c47',
content: commentContent,
},
relatedUser: null,
},
],
},
@ -616,4 +633,232 @@ describe('notifications', () => {
})
})
})
describe('group notifications', () => {
let groupOwner
beforeEach(async () => {
groupOwner = await neode.create(
'User',
{
id: 'group-owner',
name: 'Group Owner',
slug: 'group-owner',
},
{
email: 'owner@example.org',
password: '1234',
},
)
authenticatedUser = await groupOwner.toJson()
await mutate({
mutation: createGroupMutation(),
variables: {
id: 'closed-group',
name: 'The Closed Group',
about: 'Will test the closed group!',
description: 'Some description' + Array(50).join('_'),
groupType: 'public',
actionRadius: 'regional',
categoryIds,
},
})
})
describe('user joins group', () => {
beforeEach(async () => {
authenticatedUser = await notifiedUser.toJson()
await mutate({
mutation: joinGroupMutation(),
variables: {
groupId: 'closed-group',
userId: authenticatedUser.id,
},
})
authenticatedUser = await groupOwner.toJson()
})
it('has the notification in database', async () => {
await expect(
query({
query: notificationQuery,
}),
).resolves.toMatchObject({
data: {
notifications: [
{
read: false,
reason: 'user_joined_group',
createdAt: expect.any(String),
from: {
__typename: 'Group',
id: 'closed-group',
},
relatedUser: {
id: 'you',
},
},
],
},
errors: undefined,
})
})
})
describe('user leaves group', () => {
beforeEach(async () => {
authenticatedUser = await notifiedUser.toJson()
await mutate({
mutation: joinGroupMutation(),
variables: {
groupId: 'closed-group',
userId: authenticatedUser.id,
},
})
await mutate({
mutation: leaveGroupMutation(),
variables: {
groupId: 'closed-group',
userId: authenticatedUser.id,
},
})
authenticatedUser = await groupOwner.toJson()
})
it('has two the notification in database', async () => {
await expect(
query({
query: notificationQuery,
}),
).resolves.toMatchObject({
data: {
notifications: [
{
read: false,
reason: 'user_left_group',
createdAt: expect.any(String),
from: {
__typename: 'Group',
id: 'closed-group',
},
relatedUser: {
id: 'you',
},
},
{
read: false,
reason: 'user_joined_group',
createdAt: expect.any(String),
from: {
__typename: 'Group',
id: 'closed-group',
},
relatedUser: {
id: 'you',
},
},
],
},
errors: undefined,
})
})
})
describe('user role in group changes', () => {
beforeEach(async () => {
authenticatedUser = await notifiedUser.toJson()
await mutate({
mutation: joinGroupMutation(),
variables: {
groupId: 'closed-group',
userId: authenticatedUser.id,
},
})
authenticatedUser = await groupOwner.toJson()
await mutate({
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'closed-group',
userId: 'you',
roleInGroup: 'admin',
},
})
authenticatedUser = await notifiedUser.toJson()
})
it('has notification in database', async () => {
await expect(
query({
query: notificationQuery,
}),
).resolves.toMatchObject({
data: {
notifications: [
{
read: false,
reason: 'changed_group_member_role',
createdAt: expect.any(String),
from: {
__typename: 'Group',
id: 'closed-group',
},
relatedUser: {
id: 'group-owner',
},
},
],
},
errors: undefined,
})
})
})
describe('user is removed from group', () => {
beforeEach(async () => {
authenticatedUser = await notifiedUser.toJson()
await mutate({
mutation: joinGroupMutation(),
variables: {
groupId: 'closed-group',
userId: authenticatedUser.id,
},
})
authenticatedUser = await groupOwner.toJson()
await mutate({
mutation: removeUserFromGroupMutation(),
variables: {
groupId: 'closed-group',
userId: 'you',
},
})
authenticatedUser = await notifiedUser.toJson()
})
it('has notification in database', async () => {
await expect(
query({
query: notificationQuery,
}),
).resolves.toMatchObject({
data: {
notifications: [
{
read: false,
reason: 'removed_user_from_group',
createdAt: expect.any(String),
from: {
__typename: 'Group',
id: 'closed-group',
},
relatedUser: {
id: 'group-owner',
},
},
],
},
errors: undefined,
})
})
})
})
})

View File

@ -47,12 +47,22 @@ export default {
`
MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id})
${whereClause}
WITH user, notification, resource,
OPTIONAL MATCH (relatedUser:User { id: notification.relatedUserId })
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(relatedUser)
WITH user, notification, resource, membership, relatedUser,
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
RETURN notification {.*, from: finalResource, to: properties(user)}
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts, relatedUser, membership,
resource {.*,
__typename: labels(resource)[0],
author: authors[0],
post: posts[0],
myRole: membership.role } AS finalResource
RETURN notification {.*,
from: finalResource,
to: properties(user),
relatedUser: properties(relatedUser)
}
${orderByClause}
${offset} ${limit}
`,
@ -81,8 +91,9 @@ export default {
WITH user, notification, resource,
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user)
WITH resource, user, notification, authors, posts, membership,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], myRole: membership.role } AS finalResource
RETURN notification {.*, from: finalResource, to: properties(user)}
`,
{ resourceId: args.id, id: currentUser.id },
@ -110,8 +121,9 @@ export default {
WITH user, notification, resource,
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user)
WITH resource, user, notification, authors, posts, membership,
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], myRole: membership.role} AS finalResource
RETURN notification {.*, from: finalResource, to: properties(user)}
`,
{ id: currentUser.id },

View File

@ -397,18 +397,20 @@ describe('given some notifications', () => {
it('returns all as read', async () => {
const response = await mutate({ mutation: markAllAsReadMutation(), variables })
expect(response.data.markAllAsRead).toEqual([
{
createdAt: '2019-08-30T19:33:48.651Z',
from: { __typename: 'Comment', content: 'You have been mentioned in a comment' },
read: true,
},
{
createdAt: '2019-08-31T17:33:48.651Z',
from: { __typename: 'Post', content: 'You have been mentioned in a post' },
read: true,
},
])
expect(response.data.markAllAsRead).toEqual(
expect.arrayContaining([
{
createdAt: '2019-08-30T19:33:48.651Z',
from: { __typename: 'Comment', content: 'You have been mentioned in a comment' },
read: true,
},
{
createdAt: '2019-08-31T17:33:48.651Z',
from: { __typename: 'Post', content: 'You have been mentioned in a post' },
read: true,
},
]),
)
expect(response.errors).toBeUndefined()
})
})

View File

@ -1,5 +0,0 @@
enum ReasonNotification {
mentioned_in_post
mentioned_in_comment
commented_on_post
}

View File

@ -6,9 +6,10 @@ type NOTIFIED {
updatedAt: String!
read: Boolean
reason: NotificationReason
relatedUser: User
}
union NotificationSource = Post | Comment
union NotificationSource = Post | Comment | Group
enum NotificationOrdering {
createdAt_asc
@ -21,6 +22,10 @@ enum NotificationReason {
mentioned_in_post
mentioned_in_comment
commented_on_post
user_joined_group
user_left_group
changed_group_member_role
removed_user_from_group
}
type Query {

View File

@ -63,7 +63,7 @@ export default {
content: this.$t('group.joinLeaveButton.tooltip'),
placement: 'right',
show: this.isMember && !this.isNonePendingMember && this.hovered,
trigger: this.isMember && !this.isNonePendingMember ? 'hover' : 'manual',
trigger: 'manual',
}
},
},

View File

@ -41,7 +41,7 @@
{{ formData.title.length }}/{{ formSchema.title.max }}
<base-icon v-if="errors && errors.title" name="warning" />
</ds-chip>
<hc-editor
<editor
:users="users"
:value="formData.content"
:hashtags="hashtags"
@ -64,14 +64,30 @@
{{ formData.categoryIds.length }} / 3
<base-icon v-if="errors && errors.categoryIds" name="warning" />
</ds-chip>
<div class="buttons">
<base-button data-test="cancel-button" :disabled="loading" @click="$router.back()" danger>
{{ $t('actions.cancel') }}
</base-button>
<base-button type="submit" icon="check" :loading="loading" :disabled="errors" filled>
{{ $t('actions.save') }}
</base-button>
</div>
<ds-flex class="buttons-footer" gutter="xxx-small">
<ds-flex-item width="3.5" style="margin-right: 16px; margin-bottom: 6px">
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<ds-text
v-if="showGroupHint"
v-html="$t('contribution.visibleOnlyForMembersOfGroup', { name: groupName })"
/>
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
</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
>
{{ $t('actions.cancel') }}
</base-button>
<base-button type="submit" icon="check" :loading="loading" :disabled="errors" filled>
{{ $t('actions.save') }}
</base-button>
</ds-flex-item>
</ds-flex>
</base-card>
</template>
</ds-form>
@ -80,7 +96,7 @@
<script>
import gql from 'graphql-tag'
import { mapGetters } from 'vuex'
import HcEditor from '~/components/Editor/Editor'
import Editor from '~/components/Editor/Editor'
import PostMutations from '~/graphql/PostMutations.js'
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
import ImageUploader from '~/components/Uploader/ImageUploader'
@ -89,7 +105,7 @@ import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParams
export default {
components: {
HcEditor,
Editor,
ImageUploader,
PageParamsLink,
CategoriesSelect,
@ -99,8 +115,8 @@ export default {
type: Object,
default: () => ({}),
},
groupId: {
type: String,
group: {
type: Object,
default: () => null,
},
},
@ -152,6 +168,15 @@ export default {
contentLength() {
return this.$filters.removeHtml(this.formData.content).length
},
groupId() {
return this.group && this.group.id
},
showGroupHint() {
return this.groupId && ['closed', 'hidden'].includes(this.group.groupType)
},
groupName() {
return this.group && this.group.name
},
},
methods: {
submit() {
@ -284,9 +309,22 @@ export default {
align-self: flex-end;
}
> .buttons {
> .buttons-footer {
justify-content: flex-end;
align-self: flex-end;
width: 100%;
margin-top: $space-base;
> .action-buttons-group {
margin-left: auto;
display: flex;
justify-content: flex-end;
> button {
margin-left: 1em;
min-width: fit-content;
}
}
}
.blur-toggle {
@ -297,5 +335,30 @@ export default {
display: block;
}
}
@media screen and (max-width: 656px) {
> .buttons-footer {
flex-direction: column;
margin-top: 5px;
> .action-buttons-group {
> button {
margin-left: 1em;
}
}
}
}
@media screen and (max-width: 280px) {
> .buttons-footer {
> .action-buttons-group {
flex-direction: column;
> button {
margin-bottom: 5px;
}
}
}
}
}
</style>

View File

@ -1,19 +1,24 @@
<template>
<article :class="{ '--read': notification.read, notification: true }">
<client-only>
<user-teaser :user="from.author" :date-time="from.createdAt" />
<user-teaser
:user="isGroup ? notification.relatedUser : from.author"
:date-time="from.createdAt"
/>
</client-only>
<p class="description">{{ $t(`notifications.reason.${notification.reason}`) }}</p>
<nuxt-link
class="link"
:to="{ name: 'post-id-slug', params, ...hashParam }"
:to="{ name: isGroup ? 'group-id-slug' : 'post-id-slug', params, hashParam }"
@click.native="$emit('read')"
>
<base-card wideContent>
<h2 class="title">{{ from.title || from.post.title }}</h2>
<h2 class="title">{{ from.title || from.groupName || from.post.title }}</h2>
<p>
<strong v-if="isComment" class="comment">{{ $t(`notifications.comment`) }}:</strong>
{{ from.contentExcerpt | removeHtml }}
<strong v-if="isGroup" class="comment">{{ $t(`notifications.group`) }}:</strong>
{{ from.descriptionExcerpt | removeHtml }}
</p>
</base-card>
</nuxt-link>
@ -41,11 +46,14 @@ export default {
isComment() {
return this.from.__typename === 'Comment'
},
isGroup() {
return this.from.__typename === 'Group'
},
params() {
const post = this.isComment ? this.from.post : this.from
const target = this.isComment ? this.from.post : this.from
return {
id: post.id,
slug: post.slug,
id: target.id,
slug: target.slug,
}
},
hashParam() {

View File

@ -39,7 +39,11 @@
<ds-space margin-bottom="base">
<client-only>
<user-teaser
:user="notification.from.author"
:user="
isGroup(notification.from)
? notification.relatedUser
: notification.from.author
"
:date-time="notification.from.createdAt"
:class="{ 'notification-status': notification.read }"
/>
@ -61,14 +65,18 @@
class="notification-mention-post"
:class="{ 'notification-status': notification.read }"
:to="{
name: 'post-id-slug',
name: isGroup(notification.from) ? 'group-id-slug' : 'post-id-slug',
params: params(notification.from),
hash: hashParam(notification.from),
}"
@click.native="markNotificationAsRead(notification.from.id)"
>
<b>
{{ notification.from.title || notification.from.post.title | truncate(50) }}
{{
notification.from.title ||
notification.from.groupName ||
notification.from.post.title | truncate(50)
}}
</b>
</nuxt-link>
</base-card>
@ -76,7 +84,10 @@
<ds-flex-item>
<base-card :wide-content="true">
<b :class="{ 'notification-status': notification.read }">
{{ notification.from.contentExcerpt | removeHtml }}
{{
notification.from.contentExcerpt ||
notification.from.descriptionExcerpt | removeHtml
}}
</b>
</base-card>
</ds-flex-item>
@ -132,11 +143,16 @@ export default {
isComment(notificationSource) {
return notificationSource.__typename === 'Comment'
},
isGroup(notificationSource) {
return notificationSource.__typename === 'Group'
},
params(notificationSource) {
const post = this.isComment(notificationSource) ? notificationSource.post : notificationSource
const target = this.isComment(notificationSource)
? notificationSource.post
: notificationSource
return {
id: post.id,
slug: post.slug,
id: target.id,
slug: target.slug,
}
},
hashParam(notificationSource) {

View File

@ -6,6 +6,7 @@ import {
userFragment,
postFragment,
commentFragment,
groupFragment,
} from './Fragments'
export const profileUserQuery = (i18n) => {
@ -113,6 +114,7 @@ export const notificationQuery = (_i18n) => {
${userFragment}
${commentFragment}
${postFragment}
${groupFragment}
query ($read: Boolean, $orderBy: NotificationOrdering, $first: Int, $offset: Int) {
notifications(read: $read, orderBy: $orderBy, first: $first, offset: $offset) {
@ -121,6 +123,9 @@ export const notificationQuery = (_i18n) => {
reason
createdAt
updatedAt
to {
...user
}
from {
__typename
... on Post {
@ -141,6 +146,12 @@ export const notificationQuery = (_i18n) => {
}
}
}
... on Group {
...group
}
}
relatedUser {
...user
}
}
}
@ -152,6 +163,7 @@ export const markAsReadMutation = (_i18n) => {
${userFragment}
${commentFragment}
${postFragment}
${groupFragment}
mutation ($id: ID!) {
markAsRead(id: $id) {
@ -177,6 +189,9 @@ export const markAsReadMutation = (_i18n) => {
}
}
}
... on Group {
...group
}
}
}
}
@ -188,6 +203,7 @@ export const markAllAsReadMutation = (_i18n) => {
${userFragment}
${commentFragment}
${postFragment}
${groupFragment}
mutation {
markAllAsRead {
@ -213,6 +229,9 @@ export const markAllAsReadMutation = (_i18n) => {
}
}
}
... on Group {
...group
}
}
}
}

View File

@ -289,7 +289,8 @@
},
"supportedFormats": "Füge ein Bild im Dateiformat JPG, PNG oder GIF ein"
},
"title": "Titel"
"title": "Titel",
"visibleOnlyForMembersOfGroup": "Dieser Beitrag wird nur für Mitglieder der Gruppe „<b>{name}</b>“ sichtbar sein."
},
"delete": {
"cancel": "Abbrechen",
@ -639,20 +640,25 @@
},
"notifications": {
"comment": "Kommentar",
"content": "Inhalt",
"content": "Inhalt oder Beschreibung",
"empty": "Bedaure, Du hast momentan keinerlei Benachrichtigungen.",
"filterLabel": {
"all": "Alle",
"read": "Gelesen",
"unread": "Ungelesen"
},
"group": "Beschreibung",
"markAllAsRead": "Markiere alle als gelesen",
"pageLink": "Alle Benachrichtigungen",
"post": "Beitrag",
"post": "Beitrag oder Gruppe",
"reason": {
"changed_group_member_role": "Hat Deine Rolle in der Gruppe geändert …",
"commented_on_post": "Hat Deinen Beitrag kommentiert …",
"mentioned_in_comment": "Hat Dich in einem Kommentar erwähnt …",
"mentioned_in_post": "Hat Dich in einem Beitrag erwähnt …"
"mentioned_in_post": "Hat Dich in einem Beitrag erwähnt …",
"removed_user_from_group": "Hat Dich aus der Gruppe entfernt …",
"user_joined_group": "Ist Deiner Gruppe beigetreten …",
"user_left_group": "Hat deine Gruppe verlassen …"
},
"title": "Benachrichtigungen",
"user": "Nutzer"

View File

@ -289,7 +289,8 @@
},
"supportedFormats": "Insert a picture of file format JPG, PNG or GIF"
},
"title": "Title"
"title": "Title",
"visibleOnlyForMembersOfGroup": "This post will only be visible to members of the “<b>{name}</b>” group."
},
"delete": {
"cancel": "Cancel",
@ -639,20 +640,25 @@
},
"notifications": {
"comment": "Comment",
"content": "Content",
"content": "Content or Description",
"empty": "Sorry, you don't have any notifications at the moment.",
"filterLabel": {
"all": "All",
"read": "Read",
"unread": "Unread"
},
"group": "Description",
"markAllAsRead": "Mark all as read",
"pageLink": "All notifications",
"post": "Post",
"post": "Post or Group",
"reason": {
"changed_group_member_role": "Changed your role in group …",
"commented_on_post": "Commented on your post …",
"mentioned_in_comment": "Mentioned you in a comment …",
"mentioned_in_post": "Mentioned you in a post …"
"mentioned_in_post": "Mentioned you in a post …",
"removed_user_from_group": "Removed you from group …",
"user_joined_group": "Joined your group …",
"user_left_group": "Left your group …"
},
"title": "Notifications",
"user": "User"

View File

@ -565,7 +565,7 @@ export default {
// },
prepareJoinLeave() {
// "membersCountStartValue" is updated to avoid counting from 0 when join/leave
this.membersCountStartValue = this.GroupMembers.length
this.membersCountStartValue = (this.GroupMembers && this.GroupMembers.length) || 0
},
updateJoinLeave({ myRoleInGroup }) {
this.Group[0].myRole = myRoleInGroup

View File

@ -9,7 +9,7 @@
<ds-space margin="large" />
<ds-flex :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', md: 5 }">
<contribution-form :groupId="groupId" />
<contribution-form :group="group" />
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', md: 1 }">&nbsp;</ds-flex-item>
</ds-flex>

View File

@ -9,7 +9,10 @@
<ds-space margin="large" />
<ds-flex :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', md: 3 }">
<contribution-form :contribution="contribution" />
<contribution-form
:contribution="contribution"
:group="contribution && contribution.group ? contribution.group : null"
/>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', md: 1 }">&nbsp;</ds-flex-item>
</ds-flex>