Merge branch '5059-epic-groups' of github.com:Ocelot-Social-Community/Ocelot-Social into 5344-add-group-members-management

# Conflicts:
#	backend/src/db/graphql/groups.js
#	backend/src/schema/resolvers/groups.js
#	webapp/components/Group/GroupCard.vue
#	webapp/components/Group/GroupForm.vue
#	webapp/components/Group/GroupTeaser.vue
#	webapp/graphql/groups.js
#	webapp/locales/de.json
#	webapp/locales/en.json
#	webapp/pages/group/edit/_id.vue
This commit is contained in:
Wolfgang Huß 2022-09-23 15:04:49 +02:00
commit 2aa85d7b05
40 changed files with 2692 additions and 785 deletions

View File

@ -1,3 +1,3 @@
// this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js`
export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 120 // with removed HTML tags
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 250 // with removed HTML tags

View File

@ -2,171 +2,202 @@ import gql from 'graphql-tag'
// ------ mutations
export const createGroupMutation = gql`
mutation (
$id: ID
$name: String!
$slug: String
$about: String
$description: String!
$groupType: GroupType!
$actionRadius: GroupActionRadius!
$categoryIds: [ID]
$locationName: String
) {
CreateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
groupType: $groupType
actionRadius: $actionRadius
categoryIds: $categoryIds
locationName: $locationName
export const createGroupMutation = () => {
return gql`
mutation (
$id: ID
$name: String!
$slug: String
$about: String
$description: String!
$groupType: GroupType!
$actionRadius: GroupActionRadius!
$categoryIds: [ID]
$locationName: String # empty string '' sets it to null
) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
groupType
actionRadius
categories {
CreateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
groupType: $groupType
actionRadius: $actionRadius
categoryIds: $categoryIds
locationName: $locationName
) {
id
slug
name
icon
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
id
slug
name
icon
}
locationName
location {
name
nameDE
nameEN
}
myRole
}
# locationName # test this as result
myRole
}
}
`
`
}
export const updateGroupMutation = gql`
mutation (
$id: ID!
$name: String
$slug: String
$about: String
$description: String
$actionRadius: GroupActionRadius
$categoryIds: [ID]
$avatar: ImageInput
$locationName: String
) {
UpdateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
actionRadius: $actionRadius
categoryIds: $categoryIds
avatar: $avatar
locationName: $locationName
export const updateGroupMutation = () => {
return gql`
mutation (
$id: ID!
$name: String
$slug: String
$about: String
$description: String
$actionRadius: GroupActionRadius
$categoryIds: [ID]
$avatar: ImageInput
$locationName: String # empty string '' sets it to null
) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
groupType
actionRadius
categories {
UpdateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
actionRadius: $actionRadius
categoryIds: $categoryIds
avatar: $avatar
locationName: $locationName
) {
id
slug
name
icon
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
id
slug
name
icon
}
# avatar # test this as result
locationName
location {
name
nameDE
nameEN
}
myRole
}
# avatar # test this as result
# locationName # test this as result
myRole
}
}
`
`
}
export const joinGroupMutation = gql`
mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
export const joinGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}
export const leaveGroupMutation = gql`
mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
export const leaveGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}
export const changeGroupMemberRoleMutation = gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id
name
slug
myRoleInGroup
export const changeGroupMemberRoleMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}
// ------ queries
export const groupQuery = gql`
query ($isMember: Boolean, $id: ID, $slug: String) {
Group(isMember: $isMember, id: $id, slug: $slug) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
export const groupQuery = () => {
return gql`
query ($isMember: Boolean, $id: ID, $slug: String) {
Group(isMember: $isMember, id: $id, slug: $slug) {
id
slug
name
icon
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
id
slug
name
icon
}
avatar {
url
}
locationName
location {
name
nameDE
nameEN
}
myRole
}
avatar {
url
}
# locationName # test this as result
myRole
}
}
`
`
}
export const groupMembersQuery = gql`
query ($id: ID!) {
GroupMembers(id: $id) {
id
name
slug
myRoleInGroup
export const groupMembersQuery = () => {
return gql`
query ($id: ID!) {
GroupMembers(id: $id) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}

View File

@ -305,7 +305,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await peterLustig.toJson()
await Promise.all([
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
id: 'g0',
name: 'Investigative Journalism',
@ -313,27 +313,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
description: `<p class=""><em>English:</em></p><p class="">This group is hidden.</p><h3>What is our group for?</h3><p>This group was created to allow investigative journalists to share and collaborate.</p><h3>How does it work?</h3><p>Here you can internally share posts and comments about them.</p><p><br></p><p><em>Deutsch:</em></p><p class="">Diese Gruppe ist verborgen.</p><h3>Wofür ist unsere Gruppe?</h3><p class="">Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.</p><h3>Wie funktioniert das?</h3><p class="">Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.</p>`,
groupType: 'hidden',
actionRadius: 'global',
categoryIds: ['cat6', 'cat9', 'cat14'],
categoryIds: ['cat6', 'cat12', 'cat16'],
locationName: 'Hamburg, Germany',
},
}),
])
await Promise.all([
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g0',
userId: 'u2',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g0',
userId: 'u4',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g0',
userId: 'u6',
@ -342,7 +343,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
])
await Promise.all([
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g0',
userId: 'u2',
@ -350,7 +351,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
}),
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g0',
userId: 'u4',
@ -362,7 +363,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await jennyRostock.toJson()
await Promise.all([
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
id: 'g1',
name: 'School For Citizens',
@ -370,41 +371,42 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
description: `<p class=""><em>English</em></p><h3>Our goal</h3><p>Only those who enjoy learning and do not lose their curiosity can obtain a good education for life and continue to learn with joy throughout their lives.</p><h3>Curiosity</h3><p>For this we need a school that takes up the curiosity of the children, the people, and satisfies it through a lot of experience.</p><p><br></p><p><em>Deutsch</em></p><h3>Unser Ziel</h3><p class="">Nur wer Spaß am Lernen hat und seine Neugier nicht verliert, kann gute Bildung für's Leben erlangen und sein ganzes Leben mit Freude weiter lernen.</p><h3>Neugier</h3><p class="">Dazu benötigen wir eine Schule, die die Neugier der Kinder, der Menschen, aufnimmt und durch viel Erfahrung befriedigt.</p>`,
groupType: 'closed',
actionRadius: 'national',
categoryIds: ['cat7', 'cat9', 'cat16'],
categoryIds: ['cat8', 'cat14'],
locationName: 'France',
},
}),
])
await Promise.all([
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g1',
userId: 'u1',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g1',
userId: 'u2',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g1',
userId: 'u5',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g1',
userId: 'u6',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g1',
userId: 'u7',
@ -413,7 +415,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
])
await Promise.all([
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g1',
userId: 'u1',
@ -421,7 +423,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
}),
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g1',
userId: 'u5',
@ -429,7 +431,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
}),
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g1',
userId: 'u6',
@ -441,7 +443,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authenticatedUser = await bobDerBaumeister.toJson()
await Promise.all([
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
id: 'g2',
name: 'Yoga Practice',
@ -449,41 +451,41 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
description: `<h3>What Is yoga?</h3><p>Yoga is not just about practicing asanas. It's about how we do it.</p><p class="">And practicing asanas doesn't have to be yoga, it can be more athletic than yogic.</p><h3>What makes practicing asanas yogic?</h3><p class="">The important thing is:</p><ul><li><p>Use the exercises (consciously) for your personal development.</p></li></ul>`,
groupType: 'public',
actionRadius: 'interplanetary',
categoryIds: ['cat3', 'cat13', 'cat16'],
categoryIds: ['cat4', 'cat5', 'cat17'],
},
}),
])
await Promise.all([
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g2',
userId: 'u3',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g2',
userId: 'u4',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g2',
userId: 'u5',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g2',
userId: 'u6',
},
}),
mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables: {
groupId: 'g2',
userId: 'u7',
@ -492,31 +494,31 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
])
await Promise.all([
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g2',
userId: 'u3',
roleInGroup: 'usual',
},
}),
mutate({
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g2',
userId: 'u4',
roleInGroup: 'pending',
},
}),
mutate({
mutation: changeGroupMemberRoleMutation,
variables: {
groupId: 'g2',
userId: 'u4',
roleInGroup: 'usual',
},
}),
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g2',
userId: 'u5',
roleInGroup: 'usual',
roleInGroup: 'admin',
},
}),
mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: {
groupId: 'g2',
userId: 'u6',

View File

@ -82,7 +82,7 @@ const isAllowedToChangeGroupSettings = rule({
}
})
const isAllowedSeeingMembersOfGroup = rule({
const isAllowedSeeingGroupMembers = rule({
cache: 'no_cache',
})(async (_parent, args, { user, driver }) => {
if (!(user && user.id)) return false
@ -284,7 +284,7 @@ export default shield(
statistics: allow,
currentUser: allow,
Group: isAuthenticated,
GroupMembers: isAllowedSeeingMembersOfGroup,
GroupMembers: isAllowedSeeingGroupMembers,
Post: allow,
profilePagePosts: allow,
Comment: allow,

View File

@ -51,6 +51,7 @@ beforeEach(async () => {
await Factory.build('category', {
id: 'cat9',
name: 'Democracy & Politics',
slug: 'democracy-politics',
icon: 'university',
})
authenticatedUser = await admin.toJson()
@ -79,7 +80,7 @@ describe('slugifyMiddleware', () => {
it('generates a slug based on name', async () => {
await expect(
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables,
}),
).resolves.toMatchObject({
@ -93,13 +94,14 @@ describe('slugifyMiddleware', () => {
actionRadius: 'national',
},
},
errors: undefined,
})
})
it('generates a slug based on given slug', async () => {
await expect(
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
...variables,
slug: 'the-group',
@ -111,6 +113,7 @@ describe('slugifyMiddleware', () => {
slug: 'the-group',
},
},
errors: undefined,
})
})
})
@ -118,7 +121,7 @@ describe('slugifyMiddleware', () => {
describe('if slug exists', () => {
beforeEach(async () => {
await mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
...variables,
name: 'Pre-Existing Group',
@ -131,7 +134,7 @@ describe('slugifyMiddleware', () => {
it('chooses another slug', async () => {
await expect(
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
...variables,
name: 'Pre-Existing Group',
@ -144,6 +147,7 @@ describe('slugifyMiddleware', () => {
slug: 'pre-existing-group-1',
},
},
errors: undefined,
})
})
@ -152,7 +156,7 @@ describe('slugifyMiddleware', () => {
try {
await expect(
mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
...variables,
name: 'Pre-Existing Group',
@ -194,7 +198,7 @@ describe('slugifyMiddleware', () => {
beforeEach(async () => {
createGroupResult = await mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
name: 'The Best Group',
slug: 'the-best-group',
@ -213,7 +217,7 @@ describe('slugifyMiddleware', () => {
it('has the new slug', async () => {
await expect(
mutate({
mutation: updateGroupMutation,
mutation: updateGroupMutation(),
variables: {
id: createGroupResult.data.CreateGroup.id,
name: 'My Best Group',
@ -231,6 +235,7 @@ describe('slugifyMiddleware', () => {
myRole: 'owner',
},
},
errors: undefined,
})
})
})
@ -239,7 +244,7 @@ describe('slugifyMiddleware', () => {
it('has the new slug', async () => {
await expect(
mutate({
mutation: updateGroupMutation,
mutation: updateGroupMutation(),
variables: {
id: createGroupResult.data.CreateGroup.id,
slug: 'my-best-group',
@ -257,6 +262,7 @@ describe('slugifyMiddleware', () => {
myRole: 'owner',
},
},
errors: undefined,
})
})
})
@ -265,7 +271,7 @@ describe('slugifyMiddleware', () => {
describe('if new slug exists in another group', () => {
beforeEach(async () => {
await mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables: {
name: 'Pre-Existing Group',
slug: 'pre-existing-group',
@ -282,7 +288,7 @@ describe('slugifyMiddleware', () => {
it('has unique slug "*-1"', async () => {
await expect(
mutate({
mutation: updateGroupMutation,
mutation: updateGroupMutation(),
variables: {
id: createGroupResult.data.CreateGroup.id,
name: 'Pre-Existing Group',
@ -300,6 +306,7 @@ describe('slugifyMiddleware', () => {
myRole: 'owner',
},
},
errors: undefined,
})
})
})
@ -309,7 +316,7 @@ describe('slugifyMiddleware', () => {
try {
await expect(
mutate({
mutation: updateGroupMutation,
mutation: updateGroupMutation(),
variables: {
id: createGroupResult.data.CreateGroup.id,
slug: 'pre-existing-group',
@ -368,6 +375,7 @@ describe('slugifyMiddleware', () => {
slug: 'i-am-a-brand-new-post',
},
},
errors: undefined,
})
})
@ -386,6 +394,7 @@ describe('slugifyMiddleware', () => {
slug: 'the-post',
},
},
errors: undefined,
})
})
})
@ -422,6 +431,7 @@ describe('slugifyMiddleware', () => {
slug: 'pre-existing-post-1',
},
},
errors: undefined,
})
})
@ -504,6 +514,7 @@ describe('slugifyMiddleware', () => {
slug: 'i-am-a-user',
},
},
errors: undefined,
})
})
@ -522,6 +533,7 @@ describe('slugifyMiddleware', () => {
slug: 'the-user',
},
},
errors: undefined,
})
})
})
@ -546,6 +558,7 @@ describe('slugifyMiddleware', () => {
slug: 'i-am-a-user-1',
},
},
errors: undefined,
})
})

View File

@ -20,6 +20,7 @@ export default makeAugmentedSchema({
'FILED',
'REVIEWED',
'Report',
'Group',
],
},
mutation: false,

View File

@ -9,7 +9,7 @@ import Resolver, {
convertObjectToCypherMapLiteral,
} from './helpers/Resolver'
import { mergeImage } from './images/images'
import createOrUpdateLocations from './users/location'
import { createOrUpdateLocations } from './users/location'
export default {
Query: {
@ -86,6 +86,7 @@ export default {
CreateGroup: async (_parent, params, context, _resolveInfo) => {
const { categoryIds } = params
delete params.categoryIds
params.locationName = params.locationName === '' ? null : params.locationName
if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) {
throw new UserInputError('Too view categories!')
}
@ -137,7 +138,8 @@ export default {
})
try {
const group = await writeTxResultPromise
await createOrUpdateLocations(params.id, params.locationName, session)
// TODO: put in a middleware, see "UpdateGroup", "UpdateUser"
await createOrUpdateLocations('Group', params.id, params.locationName, session)
return group
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
@ -149,9 +151,11 @@ export default {
},
UpdateGroup: async (_parent, params, context, _resolveInfo) => {
const { categoryIds } = params
const { id: groupId, avatar: avatarInput } = params
delete params.categoryIds
const { id: groupId, avatar: avatarInput } = params
delete params.avatar
params.locationName = params.locationName === '' ? null : params.locationName
if (CONFIG.CATEGORIES_ACTIVE && categoryIds) {
if (categoryIds.length < CATEGORIES_MIN) {
throw new UserInputError('Too view categories!')
@ -210,7 +214,8 @@ export default {
})
try {
const group = await writeTxResultPromise
await createOrUpdateLocations(params.id, params.locationName, session)
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
await createOrUpdateLocations('Group', params.id, params.locationName, session)
return group
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ import { UserInputError, ForbiddenError } from 'apollo-server'
import { mergeImage, deleteImage } from './images/images'
import Resolver from './helpers/Resolver'
import log from './helpers/databaseLogger'
import createOrUpdateLocations from './users/location'
import { createOrUpdateLocations } from './users/location'
const neode = getNeode()
@ -139,9 +139,10 @@ export default {
return blockedUser.toJson()
},
UpdateUser: async (_parent, params, context, _resolveInfo) => {
const { termsAndConditionsAgreedVersion } = params
const { avatar: avatarInput } = params
delete params.avatar
params.locationName = params.locationName === '' ? null : params.locationName
const { termsAndConditionsAgreedVersion } = params
if (termsAndConditionsAgreedVersion) {
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
if (!regEx.test(termsAndConditionsAgreedVersion)) {
@ -169,7 +170,8 @@ export default {
})
try {
const user = await writeTxResultPromise
await createOrUpdateLocations(params.id, params.locationName, session)
// TODO: put in a middleware, see "CreateGroup", "UpdateGroup"
await createOrUpdateLocations('User', params.id, params.locationName, session)
return user
} catch (error) {
throw new UserInputError(error.message)

View File

@ -161,7 +161,7 @@ describe('UpdateUser', () => {
$id: ID!
$name: String
$termsAndConditionsAgreedVersion: String
$locationName: String
$locationName: String # empty string '' sets it to null
) {
UpdateUser(
id: $id
@ -174,6 +174,11 @@ describe('UpdateUser', () => {
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
locationName
location {
name
nameDE
nameEN
}
}
}
`
@ -289,11 +294,39 @@ describe('UpdateUser', () => {
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
})
it('supports updating location', async () => {
variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
data: { UpdateUser: { locationName: 'Hamburg, New Jersey, United States' } },
errors: undefined,
describe('supports updating location', () => {
describe('change location to "Hamburg, New Jersey, United States"', () => {
it('has updated location to "Hamburg, New Jersey, United States"', async () => {
variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
data: {
UpdateUser: {
locationName: 'Hamburg, New Jersey, United States',
location: expect.objectContaining({
name: 'Hamburg',
nameDE: 'Hamburg',
nameEN: 'Hamburg',
}),
},
},
errors: undefined,
})
})
})
describe('change location to unset location', () => {
it('has updated location to unset location', async () => {
variables = { ...variables, locationName: '' }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
data: {
UpdateUser: {
locationName: null,
location: null,
},
},
errors: undefined,
})
})
})
})
})

View File

@ -1,6 +1,5 @@
import request from 'request'
import { UserInputError } from 'apollo-server'
import isEmpty from 'lodash/isEmpty'
import Debug from 'debug'
import asyncForEach from '../../../helpers/asyncForEach'
import CONFIG from '../../../config'
@ -62,77 +61,86 @@ const createLocation = async (session, mapboxData) => {
})
}
const createOrUpdateLocations = async (userId, locationName, session) => {
if (isEmpty(locationName)) {
return
}
const res = await fetch(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
locationName,
)}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${locales.join(
',',
)}`,
)
export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, session) => {
if (locationName === undefined) return
debug(res)
let locationId
if (!res || !res.features || !res.features[0]) {
throw new UserInputError('locationName is invalid')
}
if (locationName !== null) {
const res = await fetch(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
locationName,
)}.json?access_token=${
CONFIG.MAPBOX_TOKEN
}&types=region,place,country&language=${locales.join(',')}`,
)
let data
debug(res)
res.features.forEach((item) => {
if (item.matching_place_name === locationName) {
data = item
if (!res || !res.features || !res.features[0]) {
throw new UserInputError('locationName is invalid')
}
})
if (!data) {
data = res.features[0]
}
if (!data || !data.place_type || !data.place_type.length) {
throw new UserInputError('locationName is invalid')
}
let data
if (data.place_type.length > 1) {
data.id = 'region.' + data.id.split('.')[1]
}
await createLocation(session, data)
let parent = data
if (data.context) {
await asyncForEach(data.context, async (ctx) => {
await createLocation(session, ctx)
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
MERGE (child)<-[:IS_IN]-(parent)
RETURN child.id, parent.id
`,
{
parentId: parent.id,
childId: ctx.id,
},
)
})
parent = ctx
res.features.forEach((item) => {
if (item.matching_place_name === locationName) {
data = item
}
})
if (!data) {
data = res.features[0]
}
if (!data || !data.place_type || !data.place_type.length) {
throw new UserInputError('locationName is invalid')
}
if (data.place_type.length > 1) {
data.id = 'region.' + data.id.split('.')[1]
}
await createLocation(session, data)
let parent = data
if (data.context) {
await asyncForEach(data.context, async (ctx) => {
await createLocation(session, ctx)
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
MERGE (child)<-[:IS_IN]-(parent)
RETURN child.id, parent.id
`,
{
parentId: parent.id,
childId: ctx.id,
},
)
})
parent = ctx
})
}
locationId = data.id
} else {
locationId = 'non-existent-id'
}
// delete all current locations from user and add new location
// delete all current locations from node and add new location
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location)
DETACH DELETE relationship
WITH user
MATCH (location:Location {id: $locationId})
MERGE (user)-[:IS_IN]->(location)
RETURN location.id, user.id
`,
{ userId: userId, locationId: data.id },
MATCH (node:${nodeLabel} {id: $nodeId})
OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location)
DELETE relationship
WITH node
MATCH (location:Location {id: $locationId})
MERGE (node)-[:IS_IN]->(location)
RETURN location.id, node.id
`,
{ nodeId, locationId },
)
})
}
@ -147,5 +155,3 @@ export const queryLocations = async ({ place, lang }) => {
}
return res.features
}
export default createOrUpdateLocations

View File

@ -33,8 +33,8 @@ type Group {
groupType: GroupType!
actionRadius: GroupActionRadius!
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
locationName: String
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT")
@ -95,7 +95,7 @@ type Mutation {
actionRadius: GroupActionRadius!
categoryIds: [ID]
# avatar: ImageInput # a group can not be created with an avatar
locationName: String # test this as result
locationName: String # empty string '' sets it to null
): Group
UpdateGroup(
@ -108,7 +108,7 @@ type Mutation {
actionRadius: GroupActionRadius
categoryIds: [ID]
avatar: ImageInput # test this as result
locationName: String # test this as result
locationName: String # empty string '' sets it to null
): Group
# DeleteGroup(id: ID!): Group

View File

@ -33,8 +33,8 @@ type User {
invitedBy: User @relation(name: "INVITED", direction: "IN")
invited: [User] @relation(name: "INVITED", direction: "OUT")
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
locationName: String
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
about: String
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
@ -212,7 +212,7 @@ type Mutation {
email: String
slug: String
avatar: ImageInput
locationName: String
locationName: String # empty string '' sets it to null
about: String
termsAndConditionsAgreedVersion: String
termsAndConditionsAgreedAt: String

View File

@ -1,6 +1,6 @@
<template>
<base-button
class="track-button"
class="join-leave-button"
:disabled="disabled"
:loading="localLoading"
:icon="icon"
@ -108,7 +108,7 @@ export default {
},
async joinLeave() {
const join = !this.isMember
const mutation = join ? joinGroupMutation : leaveGroupMutation
const mutation = join ? joinGroupMutation() : leaveGroupMutation()
this.hovered = false
this.$emit('prepare', join)
@ -129,7 +129,7 @@ export default {
</script>
<style lang="scss">
.track-button {
.join-leave-button {
display: block;
width: 100%;
}

View File

@ -12,7 +12,6 @@
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
delay: { show: 1500 },
}"
>
{{ $t(`contribution.category.name.${category.slug}`) }}

View File

@ -20,7 +20,6 @@
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
delay: { show: 1500 },
}"
/>
</li>

View File

@ -76,7 +76,7 @@ export default {
const variables = { groupId: id, userId: this.$store.getters['auth/user'].id }
try {
await this.$apollo.mutate({
mutation: joinGroupMutation,
mutation: joinGroupMutation(),
variables,
})
this.$toast.success(this.$t('group.groupCreated'))

View File

@ -71,7 +71,7 @@ export default {
const newRole = event.target.value
try {
await this.$apollo.mutate({
mutation: changeGroupMemberRoleMutation,
mutation: changeGroupMemberRoleMutation(),
variables: { groupId: this.groupId, userId: id, roleInGroup: newRole },
})
this.$toast.success(this.$t('group.changeMemberRole', { role: newRole }))

View File

@ -31,9 +31,8 @@
v-for="category in post.categories"
:key="category.id"
v-tooltip="{
content: $t(`contribution.category.name.${category.slug}`),
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
delay: { show: 1500 },
}"
:icon="category.icon"
/>

View File

@ -29,6 +29,7 @@ describe('AvatarUploader', () => {
profile: {
avatar: { url: '/api/generic.jpg' },
},
updateMutation: jest.fn(),
}
beforeEach(() => {
@ -40,7 +41,7 @@ describe('AvatarUploader', () => {
jest.clearAllMocks()
})
it('sends a the UpdateUser mutation when vddrop is called', () => {
it('sends the UpdateUser mutation when vddrop is called', () => {
wrapper.vm.vddrop([{ filename: 'avatar.jpg' }])
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="avatar-uploader">
<vue-dropzone
id="customdropzone"
:key="avatarUrl"
@ -29,7 +29,7 @@ export default {
},
props: {
profile: { type: Object, required: true },
updateMutation: { type: Object, required: true },
updateMutation: { type: Function, required: true },
},
data() {
return {
@ -69,7 +69,7 @@ export default {
const avatarUpload = file[0]
this.$apollo
.mutate({
mutation: this.updateMutation,
mutation: this.updateMutation(),
variables: {
avatar: {
upload: avatarUpload,

View File

@ -1,7 +1,7 @@
<template>
<button
:class="buttonClass"
:disabled="loading"
:disabled="disabled || loading"
:type="type"
@click.capture="(event) => $emit('click', event)"
>
@ -56,6 +56,10 @@ export default {
return value.match(/(button|submit)/)
},
},
disabled: {
// type: Boolean, // makes some errors that an Object was passed instead a Boolean and could not find how to solve in a acceptable time
default: false,
},
},
computed: {
buttonClass() {

View File

@ -12,6 +12,7 @@ export const userFragment = gql`
deleted
}
`
export const locationAndBadgesFragment = (lang) => gql`
fragment locationAndBadges on User {
location {

View File

@ -221,25 +221,25 @@ export const updateUserMutation = () => {
$id: ID!
$slug: String
$name: String
$locationName: String
$about: String
$allowEmbedIframes: Boolean
$showShoutsPublicly: Boolean
$sendNotificationEmails: Boolean
$termsAndConditionsAgreedVersion: String
$avatar: ImageInput
$locationName: String # empty string '' sets it to null
) {
UpdateUser(
id: $id
slug: $slug
name: $name
locationName: $locationName
about: $about
allowEmbedIframes: $allowEmbedIframes
showShoutsPublicly: $showShoutsPublicly
sendNotificationEmails: $sendNotificationEmails
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
avatar: $avatar
locationName: $locationName
) {
id
slug

View File

@ -2,171 +2,191 @@ import gql from 'graphql-tag'
// ------ mutations
export const createGroupMutation = gql`
mutation (
$id: ID
$name: String!
$slug: String
$about: String
$description: String!
$groupType: GroupType!
$actionRadius: GroupActionRadius!
$categoryIds: [ID]
$locationName: String
) {
CreateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
groupType: $groupType
actionRadius: $actionRadius
categoryIds: $categoryIds
locationName: $locationName
export const createGroupMutation = () => {
return gql`
mutation (
$id: ID
$name: String!
$slug: String
$about: String
$description: String!
$groupType: GroupType!
$actionRadius: GroupActionRadius!
$categoryIds: [ID]
$locationName: String # empty string '' sets it to null
) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
groupType
actionRadius
categories {
CreateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
groupType: $groupType
actionRadius: $actionRadius
categoryIds: $categoryIds
locationName: $locationName
) {
id
slug
name
icon
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
id
slug
name
icon
}
locationName
myRole
}
locationName # test this as result
myRole
}
}
`
`
}
export const updateGroupMutation = gql`
mutation (
$id: ID!
$name: String
$slug: String
$about: String
$description: String
$actionRadius: GroupActionRadius
$categoryIds: [ID]
$avatar: ImageInput
$locationName: String
) {
UpdateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
actionRadius: $actionRadius
categoryIds: $categoryIds
avatar: $avatar
locationName: $locationName
export const updateGroupMutation = () => {
return gql`
mutation (
$id: ID!
$name: String
$slug: String
$about: String
$description: String
$actionRadius: GroupActionRadius
$categoryIds: [ID]
$avatar: ImageInput
$locationName: String # empty string '' sets it to null
) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
groupType
actionRadius
categories {
UpdateGroup(
id: $id
name: $name
slug: $slug
about: $about
description: $description
actionRadius: $actionRadius
categoryIds: $categoryIds
avatar: $avatar
locationName: $locationName
) {
id
slug
name
icon
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
id
slug
name
icon
}
# avatar # test this as result
locationName
myRole
}
# avatar # test this as result
# locationName # test this as result
myRole
}
}
`
`
}
export const joinGroupMutation = gql`
mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
export const joinGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}
export const leaveGroupMutation = gql`
mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
export const leaveGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}
export const changeGroupMemberRoleMutation = gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id
name
slug
myRoleInGroup
export const changeGroupMemberRoleMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}
// ------ queries
export const groupQuery = gql`
query ($isMember: Boolean, $id: ID, $slug: String) {
Group(isMember: $isMember, id: $id, slug: $slug) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
export const groupQuery = (i18n) => {
const lang = i18n ? i18n.locale().toUpperCase() : 'EN'
return gql`
query ($isMember: Boolean, $id: ID, $slug: String) {
Group(isMember: $isMember, id: $id, slug: $slug) {
id
slug
name
icon
slug
createdAt
updatedAt
disabled
deleted
about
description
descriptionExcerpt
groupType
actionRadius
categories {
id
slug
name
icon
}
avatar {
url
}
locationName
location {
name: name${lang}
}
myRole
}
avatar {
url
}
locationName # test this as result
myRole
}
}
`
`
}
export const groupMembersQuery = gql`
query ($id: ID!) {
GroupMembers(id: $id) {
id
name
slug
myRoleInGroup
export const groupMembersQuery = () => {
return gql`
query ($id: ID!) {
GroupMembers(id: $id) {
id
name
slug
myRoleInGroup
}
}
}
`
`
}

View File

@ -405,6 +405,7 @@
},
"actionRadius": "Aktionsradius",
"back": "zurück",
"categories": "Thema ::: Themen",
"change-member-role": "Die Rolle wurde auf „{role}“ geändert!",
"follow": "Folge",
"foundation": "Gründung",
@ -424,8 +425,9 @@
},
"long-description": "Beschreibung",
"members": "Mitglieder",
"membersCount": "Mitglieder",
"membersCount": "Mitglied ::: Mitglieder",
"membersListTitle": "Gruppenmitglieder",
"membersListTitleNotAllowedSeeingGroupMembers": "Gruppenmitglieder unsichtbar",
"myGroups": "Meine Gruppen",
"newGroup": "Erstelle eine neue Gruppe",
"radius": "Radius",

View File

@ -405,6 +405,7 @@
},
"actionRadius": "Action radius",
"back": "back",
"categories": "Topic ::: Topics",
"change-member-role": "The role has been changed to “{role}”!",
"follow": "Follow",
"foundation": "Foundation",
@ -424,8 +425,9 @@
},
"long-description": "Description",
"members": "Members",
"membersCount": "Members",
"membersCount": "Member ::: Members",
"membersListTitle": "Group Members",
"membersListTitleNotAllowedSeeingGroupMembers": "Group Members invisible",
"myGroups": "My Groups",
"newGroup": "Create a new Group",
"radius": "Radius",

View File

@ -1,95 +0,0 @@
// import { config, mount } from '@vue/test-utils'
// import ProfileSlug from './_slug.vue'
// const localVue = global.localVue
// localVue.filter('date', (d) => d)
// config.stubs['client-only'] = '<span><slot /></span>'
// config.stubs['v-popover'] = '<span><slot /></span>'
// config.stubs['nuxt-link'] = '<span><slot /></span>'
// config.stubs['infinite-loading'] = '<span><slot /></span>'
// config.stubs['follow-list'] = '<span><slot /></span>'
// describe('ProfileSlug', () => {
// let wrapper
// let Wrapper
// let mocks
// beforeEach(() => {
// mocks = {
// post: {
// id: 'p23',
// name: 'It is a post',
// },
// $t: jest.fn(),
// // If you're mocking router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
// $route: {
// params: {
// id: '4711',
// slug: 'john-doe',
// },
// },
// $router: {
// history: {
// push: jest.fn(),
// },
// },
// $toast: {
// success: jest.fn(),
// error: jest.fn(),
// },
// $apollo: {
// loading: false,
// mutate: jest.fn().mockResolvedValue(),
// },
// }
// })
// describe('mount', () => {
// Wrapper = () => {
// return mount(ProfileSlug, {
// mocks,
// localVue,
// })
// }
// describe('given an authenticated user', () => {
// beforeEach(() => {
// mocks.$filters = {
// removeLinks: (c) => c,
// truncate: (a) => a,
// }
// mocks.$store = {
// getters: {
// 'auth/isModerator': () => false,
// 'auth/user': {
// id: 'u23',
// },
// },
// }
// })
// describe('given a user for the profile', () => {
// beforeEach(() => {
// wrapper = Wrapper()
// wrapper.setData({
// User: [
// {
// id: 'u3',
// name: 'Bob the builder',
// contributionsCount: 6,
// shoutedCount: 7,
// commentedCount: 8,
// },
// ],
// })
// })
// it('displays name of the user', () => {
// expect(wrapper.text()).toContain('Bob the builder')
// })
// })
// })
// })
// })

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
<template>
<div>
<div v-if="isGroupVisible">
<ds-space />
<ds-flex v-if="group" :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
@ -31,24 +31,29 @@
/>
</client-only> -->
<ds-space margin="small">
<!-- Group name -->
<ds-heading tag="h3" align="center" no-margin>
{{ groupName }}
</ds-heading>
<!-- Group slug -->
<ds-text align="center" color="soft">
{{ groupSlug }}
</ds-text>
<!-- <ds-text v-if="user.location" align="center" color="soft" size="small">
<base-icon name="map-marker" />
{{ user.location.name }}
</ds-text> -->
<!-- Group location -->
<ds-text v-if="group && group.location" align="center" color="soft" size="small">
<base-icon name="map-marker" data-test="map-marker" />
{{ group && group.location ? group.location.name : '' }}
</ds-text>
<!-- Group created at -->
<ds-text align="center" color="soft" size="small">
{{ $t('group.foundation') }} {{ group.createdAt | date('MMMM yyyy') }}
</ds-text>
</ds-space>
<ds-flex>
<ds-flex-item>
<ds-flex v-if="isAllowedSeeingGroupMembers">
<!-- Group members count -->
<ds-flex-item v-if="isAllowedSeeingGroupMembers">
<client-only>
<ds-number :label="$t('group.membersCount')">
<ds-number :label="$t('group.membersCount', {}, groupMembers.length)">
<count-to
slot="count"
:start-val="membersCountStartValue"
@ -90,6 +95,7 @@
@optimistic="optimisticFollow"
@update="updateFollow"
/> -->
<!-- Group join / leave -->
<join-leave-button
:group="group || {}"
:userId="currentUser.id"
@ -104,35 +110,82 @@
</div>
<hr />
<ds-space margin-top="small" margin-bottom="small">
<!-- Group my role in group -->
<template v-if="isGroupMember">
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.role') }}
</ds-text>
<div class="chip" align="center">
<ds-chip color="primary">{{ $t('group.roles.' + group.myRole) }}</ds-chip>
<ds-chip color="primary">
{{ group && group.myRole ? $t('group.roles.' + group.myRole) : '' }}
</ds-chip>
</div>
</template>
<!-- Group type -->
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.type') }}
</ds-text>
<div class="chip" align="center">
<ds-chip color="primary">{{ $t('group.types.' + group.groupType) }}</ds-chip>
<ds-chip color="primary">
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
</ds-chip>
</div>
<!-- Group action radius -->
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.actionRadius') }}
</ds-text>
<div class="chip" align="center">
<ds-chip color="primary">{{ $t('group.actionRadii.' + group.actionRadius) }}</ds-chip>
<ds-chip color="primary">
{{
group && group.actionRadius ? $t('group.actionRadii.' + group.actionRadius) : ''
}}
</ds-chip>
</div>
<ds-space margin="x-small" />
</ds-space>
<template v-if="group.about">
<!-- Group categories -->
<template v-if="categoriesActive">
<hr />
<ds-space margin-top="small" margin-bottom="small">
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{
$t(
'group.categories',
{},
group && group.categories ? group.categories.length : 0,
)
}}
</ds-text>
<ds-space margin="xx-small" />
<div class="categories">
<div
v-for="(category, index) in group.categories"
:key="category.id"
align="center"
>
<category
:icon="category.icon"
:name="$t(`contribution.category.name.${category.slug}`)"
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
}"
/>
<ds-space v-if="index < group.categories.length - 1" margin="xxx-small" />
</div>
</div>
</ds-space>
</template>
<!-- Group goal -->
<template v-if="group && group.about">
<hr />
<ds-space margin-top="small" margin-bottom="small">
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
{{ $t('group.goal') }}
</ds-text>
<ds-space margin="xx-small" />
<div class="chip" align="center">
<ds-chip>{{ group.about }}</ds-chip>
<ds-chip>{{ group ? group.about : '' }}</ds-chip>
</div>
</ds-space>
</template>
@ -143,10 +196,15 @@
</ds-heading>
<!-- Group members list -->
<profile-list
:uniqueName="`groupMembersFilter`"
:uniqueName="'groupMembersFilter'"
:title="$t('group.membersListTitle')"
:allProfilesCount="groupMembers.length"
:profiles="groupMembers"
:titleNobody="
!isAllowedSeeingGroupMembers
? $t('group.membersListTitleNotAllowedSeeingGroupMembers')
: null
"
:allProfilesCount="isAllowedSeeingGroupMembers ? groupMembers.length : 0"
:profiles="isAllowedSeeingGroupMembers ? groupMembers : []"
:loading="$apollo.loading"
@fetchAllProfiles="fetchAllMembers"
/>
@ -168,31 +226,48 @@
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
<!-- Group description -->
<ds-space>
<base-card class="group-description">
<!-- TODO: replace editor content with tiptap render view -->
<!-- eslint-disable vue/no-v-html -->
<div
v-if="isDescriptionCollapsed"
class="content hyphenate-text"
v-html="groupDescriptionExcerpt"
/>
<content-viewer v-else class="content hyphenate-text" :content="group.description" />
<!-- eslint-enable vue/no-v-html -->
<base-button
class="collaps-button"
size="small"
ghost
@click="isDescriptionCollapsed = !isDescriptionCollapsed"
>
{{ isDescriptionCollapsed ? $t('comment.show.more') : $t('comment.show.less') }}
</base-button>
</base-card>
</ds-space>
<ds-space v-if="isGroupMemberNonePending" centered>
<nuxt-link :to="{ name: 'post-create' }">
<base-button
class="profile-post-add-button"
:path="{ name: 'post-create' }"
icon="plus"
circle
filled
v-tooltip="{
content: $t('contribution.newPost'),
placement: 'left',
}"
/>
</nuxt-link>
</ds-space>
<masonry-grid>
<!-- TapNavigation -->
<!-- <tab-navigation :tabs="tabOptions" :activeTab="tabActive" @switch-tab="handleTab" /> -->
<!-- feed -->
<ds-grid-item :row-span="2" column-span="fullWidth">
<ds-space centered>
<nuxt-link :to="{ name: 'post-create' }">
<base-button
v-if="isGroupMember"
v-tooltip="{
content: $t('contribution.newPost'),
placement: 'left',
delay: { show: 500 },
}"
:path="{ name: 'post-create' }"
class="profile-post-add-button"
icon="plus"
circle
filled
/>
</nuxt-link>
</ds-space>
</ds-grid-item>
<!-- Group post feed -->
<template v-if="posts && posts.length">
<masonry-grid-item
v-for="post in posts"
@ -217,7 +292,7 @@
</template>
<template v-else>
<ds-grid-item column-span="fullWidth">
<empty margin="xx-large" icon="file" />
<empty margin="xx-large" icon="file" data-test="icon-empty" />
</ds-grid-item>
</template>
</masonry-grid>
@ -238,7 +313,9 @@ import { updateGroupMutation, groupQuery, groupMembersQuery } from '~/graphql/gr
// import UpdateQuery from '~/components/utils/UpdateQuery'
import postListActions from '~/mixins/postListActions'
import AvatarUploader from '~/components/Uploader/AvatarUploader'
import Category from '~/components/Category'
// import ContentMenu from '~/components/ContentMenu/ContentMenu'
import ContentViewer from '~/components/Editor/ContentViewer'
import CountTo from '~/components/CountTo.vue'
import Empty from '~/components/Empty/Empty'
// import FollowButton from '~/components/Button/FollowButton'
@ -263,7 +340,9 @@ import ProfileList from '~/components/features/ProfileList/ProfileList'
export default {
components: {
AvatarUploader,
Category,
// ContentMenu,
ContentViewer,
CountTo,
Empty,
// FollowButton,
@ -290,8 +369,10 @@ export default {
data() {
// const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
Group: [],
GroupMembers: [],
loadGroupMembers: false,
posts: [],
// hasMore: true,
// offset: 0,
@ -304,6 +385,7 @@ export default {
membersCountStartValue: 0,
membersCountToLoad: Infinity,
updateGroupMutation,
isDescriptionCollapsed: true,
}
},
computed: {
@ -311,16 +393,7 @@ export default {
return this.$store.getters['auth/user']
},
group() {
return this.Group[0] ? this.Group[0] : {}
},
groupMembers() {
return this.GroupMembers ? this.GroupMembers : []
},
isGroupOwner() {
return this.group ? this.group.myRole === 'owner' : false
},
isGroupMember() {
return this.group ? !!this.group.myRole : false
return this.Group && this.Group[0] ? this.Group[0] : {}
},
groupName() {
const { name } = this.group || {}
@ -330,6 +403,31 @@ export default {
const { slug } = this.group || {}
return slug && `@${slug}`
},
groupDescriptionExcerpt() {
return this.group ? this.$filters.removeLinks(this.group.descriptionExcerpt) : ''
},
isGroupOwner() {
return this.group ? this.group.myRole === 'owner' : false
},
isGroupMember() {
return this.group ? !!this.group.myRole : false
},
isGroupMemberNonePending() {
return this.group ? ['usual', 'admin', 'owner'].includes(this.group.myRole) : false
},
isGroupVisible() {
return this.group && !(this.group.groupType === 'hidden' && !this.isGroupMemberNonePending)
},
groupMembers() {
return this.GroupMembers ? this.GroupMembers : []
},
isAllowedSeeingGroupMembers() {
return (
this.group &&
(this.group.groupType === 'public' ||
(['closed', 'hidden'].includes(this.group.groupType) && this.isGroupMemberNonePending))
)
},
// tabOptions() {
// return [
// {
@ -353,6 +451,11 @@ export default {
// ]
// },
},
watch: {
isAllowedSeeingGroupMembers(to, _from) {
this.loadGroupMembers = to
},
},
methods: {
// handleTab(tab) {
// if (this.tabActive !== tab) {
@ -463,7 +566,11 @@ export default {
},
updateJoinLeave({ myRoleInGroup }) {
this.Group[0].myRole = myRoleInGroup
this.$apollo.queries.GroupMembers.refetch()
if (this.isAllowedSeeingGroupMembers) {
this.$apollo.queries.GroupMembers.refetch()
} else {
this.GroupMembers = []
}
},
fetchAllMembers() {
this.membersCountToLoad = Infinity
@ -489,8 +596,7 @@ export default {
// },
Group: {
query() {
// return groupQuery(this.$i18n) // language will be needed for locations
return groupQuery
return groupQuery(this.$i18n)
},
variables() {
return {
@ -499,17 +605,26 @@ export default {
// followingCount: this.followingCount,
}
},
error(error) {
this.$toast.error(error.message)
},
fetchPolicy: 'cache-and-network',
},
GroupMembers: {
query() {
return groupMembersQuery
return groupMembersQuery()
},
variables() {
return {
id: this.$route.params.id,
}
},
skip() {
return !this.loadGroupMembers
},
error(error) {
this.$toast.error(error.message)
},
fetchPolicy: 'cache-and-network',
},
},
@ -547,4 +662,17 @@ export default {
.chip {
margin-bottom: $space-x-small;
}
.group-description > .base-card {
display: flex;
flex-direction: column;
height: 100%;
> .content {
flex-grow: 1;
margin-bottom: $space-small;
}
}
.collaps-button {
float: right;
}
</style>

View File

@ -41,7 +41,7 @@ export default {
}
try {
await this.$apollo.mutate({
mutation: createGroupMutation,
mutation: createGroupMutation(),
variables,
})
this.$toast.success(this.$t('group.groupCreated'))

View File

@ -56,7 +56,7 @@ export default {
Group: [group],
},
} = await client.query({
query: groupQuery,
query: groupQuery(), // "this.$i18n" is undefined here, so we use default lang
variables: { id },
})
if (group.myRole !== 'owner') {

View File

@ -37,7 +37,7 @@ export default {
}
try {
await this.$apollo.mutate({
mutation: updateGroupMutation,
mutation: updateGroupMutation(),
variables,
})
this.$toast.success(this.$t('group.group-updated'))

View File

@ -28,7 +28,7 @@ export default {
async groupMembersQueryList() {
try {
const response = await this.$apollo.query({
query: groupMembersQuery,
query: groupMembersQuery(),
variables: { id: this.group.id },
})
this.responseGroupMembersQuery = response.data.GroupMembers

View File

@ -38,7 +38,6 @@
v-tooltip="{
content: $t('contribution.newPost'),
placement: 'left',
delay: { show: 500 },
}"
class="post-add-button"
icon="plus"

View File

@ -40,11 +40,12 @@ export default {
async groupListQuery() {
try {
const response = await this.$apollo.query({
query: groupQuery,
query: groupQuery(this.$i18n),
})
this.responseGroupListQuery = response.data.Group
} catch (error) {
this.responseGroupListQuery = []
this.$toast.error(error.message)
} finally {
this.pending = false
}

View File

@ -46,7 +46,6 @@
<content-viewer class="content hyphenate-text" :content="post.content" />
<!-- Categories -->
<div v-if="categoriesActive" class="categories">
<!-- eslint-enable vue/no-v-html -->
<ds-space margin="xx-large" />
<ds-space margin="xx-small" />
<hc-category
@ -57,7 +56,6 @@
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
delay: { show: 1500 },
}"
/>
</div>

View File

@ -120,7 +120,6 @@
v-tooltip="{
content: $t('contribution.newPost'),
placement: 'left',
delay: { show: 500 },
}"
:path="{ name: 'post-create' }"
class="profile-post-add-button"
@ -238,7 +237,7 @@ export default {
followedByCountStartValue: 0,
followedByCount: followListVisibleCount,
followingCount: followListVisibleCount,
updateUserMutation: updateUserMutation(),
updateUserMutation,
}
},
computed: {

View File

@ -25,6 +25,7 @@ import { VERSION } from '~/constants/terms-and-conditions-version.js'
import { updateUserMutation } from '~/graphql/User.js'
export default {
name: 'TermsAndConditionsConfirm',
layout: 'default',
head() {
return {

View File

@ -3,7 +3,7 @@ import VTooltip from 'v-tooltip'
Vue.use(VTooltip, {
defaultDelay: {
show: 500,
show: 1500,
hide: 50,
},
defaultOffset: 2,