mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into 1733-fix
This commit is contained in:
commit
333b38d1b2
13206
backend/package-lock.json
generated
Normal file
13206
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,7 @@
|
|||||||
"cheerio": "~1.0.0-rc.3",
|
"cheerio": "~1.0.0-rc.3",
|
||||||
"cors": "~2.8.5",
|
"cors": "~2.8.5",
|
||||||
"cross-env": "~6.0.3",
|
"cross-env": "~6.0.3",
|
||||||
"date-fns": "2.5.0",
|
"date-fns": "2.5.1",
|
||||||
"debug": "~4.1.1",
|
"debug": "~4.1.1",
|
||||||
"dotenv": "~8.2.0",
|
"dotenv": "~8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
@ -82,7 +82,7 @@
|
|||||||
"metascraper-lang-detector": "^4.8.5",
|
"metascraper-lang-detector": "^4.8.5",
|
||||||
"metascraper-logo": "^5.7.6",
|
"metascraper-logo": "^5.7.6",
|
||||||
"metascraper-publisher": "^5.7.6",
|
"metascraper-publisher": "^5.7.6",
|
||||||
"metascraper-soundcloud": "^5.7.6",
|
"metascraper-soundcloud": "^5.7.7",
|
||||||
"metascraper-title": "^5.7.6",
|
"metascraper-title": "^5.7.6",
|
||||||
"metascraper-url": "^5.7.6",
|
"metascraper-url": "^5.7.6",
|
||||||
"metascraper-video": "^5.7.6",
|
"metascraper-video": "^5.7.6",
|
||||||
|
|||||||
@ -134,6 +134,7 @@ const permissions = shield(
|
|||||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||||
blockedUsers: isAuthenticated,
|
blockedUsers: isAuthenticated,
|
||||||
notifications: isAuthenticated,
|
notifications: isAuthenticated,
|
||||||
|
profilePagePosts: or(onlyEnabledContent, isModerator),
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': deny,
|
'*': deny,
|
||||||
@ -174,6 +175,8 @@ const permissions = shield(
|
|||||||
markAsRead: isAuthenticated,
|
markAsRead: isAuthenticated,
|
||||||
AddEmailAddress: isAuthenticated,
|
AddEmailAddress: isAuthenticated,
|
||||||
VerifyEmailAddress: isAuthenticated,
|
VerifyEmailAddress: isAuthenticated,
|
||||||
|
pinPost: isAdmin,
|
||||||
|
unpinPost: isAdmin,
|
||||||
},
|
},
|
||||||
User: {
|
User: {
|
||||||
email: or(isMyOwn, isAdmin),
|
email: or(isMyOwn, isAdmin),
|
||||||
|
|||||||
@ -28,12 +28,18 @@ module.exports = {
|
|||||||
relationship: 'FOLLOWS',
|
relationship: 'FOLLOWS',
|
||||||
target: 'User',
|
target: 'User',
|
||||||
direction: 'out',
|
direction: 'out',
|
||||||
|
properties: {
|
||||||
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
followedBy: {
|
followedBy: {
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationship: 'FOLLOWS',
|
relationship: 'FOLLOWS',
|
||||||
target: 'User',
|
target: 'User',
|
||||||
direction: 'in',
|
direction: 'in',
|
||||||
|
properties: {
|
||||||
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
friends: { type: 'relationship', relationship: 'FRIENDS', target: 'User', direction: 'both' },
|
friends: { type: 'relationship', relationship: 'FRIENDS', target: 'User', direction: 'both' },
|
||||||
disabledBy: {
|
disabledBy: {
|
||||||
@ -98,6 +104,9 @@ module.exports = {
|
|||||||
relationship: 'SHOUTED',
|
relationship: 'SHOUTED',
|
||||||
target: 'Post',
|
target: 'Post',
|
||||||
direction: 'out',
|
direction: 'out',
|
||||||
|
properties: {
|
||||||
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
isIn: {
|
isIn: {
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
@ -105,6 +114,15 @@ module.exports = {
|
|||||||
target: 'Location',
|
target: 'Location',
|
||||||
direction: 'out',
|
direction: 'out',
|
||||||
},
|
},
|
||||||
|
pinned: {
|
||||||
|
type: 'relationship',
|
||||||
|
relationship: 'PINNED',
|
||||||
|
target: 'Post',
|
||||||
|
direction: 'out',
|
||||||
|
properties: {
|
||||||
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
},
|
||||||
|
},
|
||||||
allowEmbedIframes: {
|
allowEmbedIframes: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
|
|||||||
@ -131,6 +131,21 @@ describe('follow', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('adds `createdAt` to `FOLLOW` relationship', async () => {
|
||||||
|
await mutate({
|
||||||
|
mutation: mutationFollowUser,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
const relation = await neode.cypher(
|
||||||
|
'MATCH (user:User {id: {id}})-[relationship:FOLLOWS]->(followed:User) WHERE relationship.createdAt IS NOT NULL RETURN relationship',
|
||||||
|
{ id: 'u1' },
|
||||||
|
)
|
||||||
|
const relationshipProperties = relation.records.map(
|
||||||
|
record => record.get('relationship').properties.createdAt,
|
||||||
|
)
|
||||||
|
expect(relationshipProperties[0]).toEqual(expect.any(String))
|
||||||
|
})
|
||||||
|
|
||||||
test('I can`t follow myself', async () => {
|
test('I can`t follow myself', async () => {
|
||||||
variables.id = user1.id
|
variables.id = user1.id
|
||||||
await expect(mutate({ mutation: mutationFollowUser, variables })).resolves.toMatchObject({
|
await expect(mutate({ mutation: mutationFollowUser, variables })).resolves.toMatchObject({
|
||||||
@ -155,6 +170,7 @@ describe('follow', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unfollow user', () => {
|
describe('unfollow user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
variables = { id: user2.id }
|
variables = { id: user2.id }
|
||||||
|
|||||||
@ -86,6 +86,7 @@ export default function Resolver(type, options = {}) {
|
|||||||
}
|
}
|
||||||
return resolvers
|
return resolvers
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
...undefinedToNullResolver(undefinedToNull),
|
...undefinedToNullResolver(undefinedToNull),
|
||||||
...booleanResolver(boolean),
|
...booleanResolver(boolean),
|
||||||
|
|||||||
@ -2,10 +2,9 @@ import uuid from 'uuid/v4'
|
|||||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||||
import fileUpload from './fileUpload'
|
import fileUpload from './fileUpload'
|
||||||
import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
||||||
import { mergeWith, isArray } from 'lodash'
|
import { mergeWith, isArray, isEmpty } from 'lodash'
|
||||||
import { UserInputError } from 'apollo-server'
|
import { UserInputError } from 'apollo-server'
|
||||||
import Resolver from './helpers/Resolver'
|
import Resolver from './helpers/Resolver'
|
||||||
|
|
||||||
const filterForBlockedUsers = async (params, context) => {
|
const filterForBlockedUsers = async (params, context) => {
|
||||||
if (!context.user) return params
|
if (!context.user) return params
|
||||||
const [blockedUsers, blockedByUsers] = await Promise.all([
|
const [blockedUsers, blockedByUsers] = await Promise.all([
|
||||||
@ -29,16 +28,31 @@ const filterForBlockedUsers = async (params, context) => {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maintainPinnedPosts = params => {
|
||||||
|
const pinnedPostFilter = { pinnedBy_in: { role_in: ['admin'] } }
|
||||||
|
if (isEmpty(params.filter)) {
|
||||||
|
params.filter = { OR: [pinnedPostFilter, {}] }
|
||||||
|
} else {
|
||||||
|
params.filter = { OR: [pinnedPostFilter, { ...params.filter }] }
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
Post: async (object, params, context, resolveInfo) => {
|
Post: async (object, params, context, resolveInfo) => {
|
||||||
params = await filterForBlockedUsers(params, context)
|
params = await filterForBlockedUsers(params, context)
|
||||||
|
params = await maintainPinnedPosts(params)
|
||||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
},
|
},
|
||||||
findPosts: async (object, params, context, resolveInfo) => {
|
findPosts: async (object, params, context, resolveInfo) => {
|
||||||
params = await filterForBlockedUsers(params, context)
|
params = await filterForBlockedUsers(params, context)
|
||||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
},
|
},
|
||||||
|
profilePagePosts: async (object, params, context, resolveInfo) => {
|
||||||
|
params = await filterForBlockedUsers(params, context)
|
||||||
|
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
|
},
|
||||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const { postId, data } = params
|
const { postId, data } = params
|
||||||
@ -115,10 +129,10 @@ export default {
|
|||||||
delete params.categoryIds
|
delete params.categoryIds
|
||||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
|
||||||
let updatePostCypher = `MATCH (post:Post {id: $params.id})
|
let updatePostCypher = `MATCH (post:Post {id: $params.id})
|
||||||
SET post += $params
|
SET post += $params
|
||||||
SET post.updatedAt = toString(datetime())
|
SET post.updatedAt = toString(datetime())
|
||||||
|
WITH post
|
||||||
`
|
`
|
||||||
|
|
||||||
if (categoryIds && categoryIds.length) {
|
if (categoryIds && categoryIds.length) {
|
||||||
@ -131,10 +145,10 @@ export default {
|
|||||||
await session.run(cypherDeletePreviousRelations, { params })
|
await session.run(cypherDeletePreviousRelations, { params })
|
||||||
|
|
||||||
updatePostCypher += `
|
updatePostCypher += `
|
||||||
WITH post
|
|
||||||
UNWIND $categoryIds AS categoryId
|
UNWIND $categoryIds AS categoryId
|
||||||
MATCH (category:Category {id: categoryId})
|
MATCH (category:Category {id: categoryId})
|
||||||
MERGE (post)-[:CATEGORIZED]->(category)
|
MERGE (post)-[:CATEGORIZED]->(category)
|
||||||
|
WITH post
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,10 +225,75 @@ export default {
|
|||||||
})
|
})
|
||||||
return emoted
|
return emoted
|
||||||
},
|
},
|
||||||
|
pinPost: async (_parent, params, context, _resolveInfo) => {
|
||||||
|
let pinnedPostWithNestedAttributes
|
||||||
|
const { driver, user } = context
|
||||||
|
const session = driver.session()
|
||||||
|
const { id: userId } = user
|
||||||
|
let writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const deletePreviousRelationsResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (:User)-[previousRelations:PINNED]->(post:Post)
|
||||||
|
DELETE previousRelations
|
||||||
|
RETURN post
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
return deletePreviousRelationsResponse.records.map(record => record.get('post').properties)
|
||||||
|
})
|
||||||
|
await writeTxResultPromise
|
||||||
|
|
||||||
|
writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const pinPostTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
|
||||||
|
MATCH (post:Post {id: $params.id})
|
||||||
|
MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
|
||||||
|
RETURN post, pinned.createdAt as pinnedAt
|
||||||
|
`,
|
||||||
|
{ userId, params },
|
||||||
|
)
|
||||||
|
return pinPostTransactionResponse.records.map(record => ({
|
||||||
|
pinnedPost: record.get('post').properties,
|
||||||
|
pinnedAt: record.get('pinnedAt'),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const [transactionResult] = await writeTxResultPromise
|
||||||
|
const { pinnedPost, pinnedAt } = transactionResult
|
||||||
|
pinnedPostWithNestedAttributes = {
|
||||||
|
...pinnedPost,
|
||||||
|
pinnedAt,
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
return pinnedPostWithNestedAttributes
|
||||||
|
},
|
||||||
|
unpinPost: async (_parent, params, context, _resolveInfo) => {
|
||||||
|
let unpinnedPost
|
||||||
|
const session = context.driver.session()
|
||||||
|
const writeTxResultPromise = session.writeTransaction(async transaction => {
|
||||||
|
const unpinPostTransactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (:User)-[previousRelations:PINNED]->(post:Post {id: $params.id})
|
||||||
|
DELETE previousRelations
|
||||||
|
RETURN post
|
||||||
|
`,
|
||||||
|
{ params },
|
||||||
|
)
|
||||||
|
return unpinPostTransactionResponse.records.map(record => record.get('post').properties)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
;[unpinnedPost] = await writeTxResultPromise
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
return unpinnedPost
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Post: {
|
Post: {
|
||||||
...Resolver('Post', {
|
...Resolver('Post', {
|
||||||
undefinedToNull: ['activityId', 'objectId', 'image', 'language'],
|
undefinedToNull: ['activityId', 'objectId', 'image', 'language', 'pinnedAt'],
|
||||||
hasMany: {
|
hasMany: {
|
||||||
tags: '-[:TAGGED]->(related:Tag)',
|
tags: '-[:TAGGED]->(related:Tag)',
|
||||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||||
@ -225,6 +304,7 @@ export default {
|
|||||||
hasOne: {
|
hasOne: {
|
||||||
author: '<-[:WROTE]-(related:User)',
|
author: '<-[:WROTE]-(related:User)',
|
||||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||||
|
pinnedBy: '<-[:PINNED]-(related:User)',
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
commentsCount:
|
commentsCount:
|
||||||
|
|||||||
@ -39,7 +39,8 @@ const createPostMutation = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
const { server } = createServer({
|
const { server } = createServer({
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
@ -269,7 +270,10 @@ describe('CreatePost', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('creates a post', async () => {
|
it('creates a post', async () => {
|
||||||
const expected = { data: { CreatePost: { title: 'I am a title', content: 'Some content' } } }
|
const expected = {
|
||||||
|
data: { CreatePost: { title: 'I am a title', content: 'Some content' } },
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
)
|
)
|
||||||
@ -285,6 +289,7 @@ describe('CreatePost', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: undefined,
|
||||||
}
|
}
|
||||||
await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject(
|
await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject(
|
||||||
expected,
|
expected,
|
||||||
@ -366,7 +371,12 @@ describe('UpdatePost', () => {
|
|||||||
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
|
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||||
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||||
id
|
id
|
||||||
|
title
|
||||||
content
|
content
|
||||||
|
author {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
categories {
|
categories {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
@ -386,7 +396,6 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
variables = {
|
variables = {
|
||||||
...variables,
|
|
||||||
id: 'p9876',
|
id: 'p9876',
|
||||||
title: 'New title',
|
title: 'New title',
|
||||||
content: 'New content',
|
content: 'New content',
|
||||||
@ -395,8 +404,11 @@ describe('UpdatePost', () => {
|
|||||||
|
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
const { errors } = await mutate({ mutation: updatePostMutation, variables })
|
authenticatedUser = null
|
||||||
expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
|
expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { UpdatePost: null },
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -550,6 +562,371 @@ describe('UpdatePost', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('pin posts', () => {
|
||||||
|
const pinPostMutation = gql`
|
||||||
|
mutation($id: ID!) {
|
||||||
|
pinPost(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
content
|
||||||
|
author {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
pinnedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
pinnedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
beforeEach(async () => {
|
||||||
|
variables = { ...variables }
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
authenticatedUser = null
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { pinPost: null },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('users cannot pin posts', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { pinPost: null },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('moderators cannot pin posts', () => {
|
||||||
|
let moderator
|
||||||
|
beforeEach(async () => {
|
||||||
|
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
||||||
|
authenticatedUser = await moderator.toJson()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { pinPost: null },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('admin can pin posts', () => {
|
||||||
|
let admin
|
||||||
|
beforeEach(async () => {
|
||||||
|
admin = await user.update({
|
||||||
|
role: 'admin',
|
||||||
|
name: 'Admin',
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
authenticatedUser = await admin.toJson()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('post created by them', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await factory.create('Post', {
|
||||||
|
id: 'created-and-pinned-by-same-admin',
|
||||||
|
author: admin,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('responds with the updated Post', async () => {
|
||||||
|
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
pinPost: {
|
||||||
|
id: 'created-and-pinned-by-same-admin',
|
||||||
|
author: {
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
pinnedBy: {
|
||||||
|
id: 'current-user',
|
||||||
|
name: 'Admin',
|
||||||
|
role: 'admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets createdAt date for PINNED', async () => {
|
||||||
|
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
pinPost: {
|
||||||
|
id: 'created-and-pinned-by-same-admin',
|
||||||
|
pinnedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('post created by another admin', () => {
|
||||||
|
let otherAdmin
|
||||||
|
beforeEach(async () => {
|
||||||
|
otherAdmin = await factory.create('User', {
|
||||||
|
role: 'admin',
|
||||||
|
name: 'otherAdmin',
|
||||||
|
})
|
||||||
|
authenticatedUser = await otherAdmin.toJson()
|
||||||
|
await factory.create('Post', {
|
||||||
|
id: 'created-by-one-admin-pinned-by-different-one',
|
||||||
|
author: otherAdmin,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('responds with the updated Post', async () => {
|
||||||
|
authenticatedUser = await admin.toJson()
|
||||||
|
variables = { ...variables, id: 'created-by-one-admin-pinned-by-different-one' }
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
pinPost: {
|
||||||
|
id: 'created-by-one-admin-pinned-by-different-one',
|
||||||
|
author: {
|
||||||
|
name: 'otherAdmin',
|
||||||
|
},
|
||||||
|
pinnedBy: {
|
||||||
|
id: 'current-user',
|
||||||
|
name: 'Admin',
|
||||||
|
role: 'admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('post created by another user', () => {
|
||||||
|
it('responds with the updated Post', async () => {
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
pinPost: {
|
||||||
|
id: 'p9876',
|
||||||
|
author: {
|
||||||
|
slug: 'the-author',
|
||||||
|
},
|
||||||
|
pinnedBy: {
|
||||||
|
id: 'current-user',
|
||||||
|
name: 'Admin',
|
||||||
|
role: 'admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('removes other pinned post', () => {
|
||||||
|
let pinnedPost
|
||||||
|
beforeEach(async () => {
|
||||||
|
await factory.create('Post', {
|
||||||
|
id: 'only-pinned-post',
|
||||||
|
author: admin,
|
||||||
|
})
|
||||||
|
await mutate({ mutation: pinPostMutation, variables })
|
||||||
|
variables = { ...variables, id: 'only-pinned-post' }
|
||||||
|
await mutate({ mutation: pinPostMutation, variables })
|
||||||
|
pinnedPost = await neode.cypher(
|
||||||
|
`MATCH (:User)-[pinned:PINNED]->(post:Post) RETURN post, pinned`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaves only one pinned post at a time', async () => {
|
||||||
|
expect(pinnedPost.records).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('PostOrdering', () => {
|
||||||
|
let pinnedPost, postCreatedAfterPinnedPost, newDate, timeInPast, admin
|
||||||
|
beforeEach(async () => {
|
||||||
|
;[pinnedPost, postCreatedAfterPinnedPost] = await Promise.all([
|
||||||
|
neode.create('Post', {
|
||||||
|
id: 'im-a-pinned-post',
|
||||||
|
}),
|
||||||
|
neode.create('Post', {
|
||||||
|
id: 'i-was-created-after-pinned-post',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
newDate = new Date()
|
||||||
|
timeInPast = newDate.getDate() - 3
|
||||||
|
newDate.setDate(timeInPast)
|
||||||
|
await pinnedPost.update({
|
||||||
|
createdAt: newDate.toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
timeInPast = newDate.getDate() + 1
|
||||||
|
newDate.setDate(timeInPast)
|
||||||
|
await postCreatedAfterPinnedPost.update({
|
||||||
|
createdAt: newDate.toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
admin = await user.update({
|
||||||
|
role: 'admin',
|
||||||
|
name: 'Admin',
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
await admin.relateTo(pinnedPost, 'pinned')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pinned post appear first even when created before other posts', async () => {
|
||||||
|
const postOrderingQuery = gql`
|
||||||
|
query($orderBy: [_PostOrdering]) {
|
||||||
|
Post(orderBy: $orderBy) {
|
||||||
|
id
|
||||||
|
pinnedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
Post: [
|
||||||
|
{
|
||||||
|
id: 'im-a-pinned-post',
|
||||||
|
pinnedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'p9876',
|
||||||
|
pinnedAt: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'i-was-created-after-pinned-post',
|
||||||
|
pinnedAt: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
variables = { orderBy: ['pinnedAt_asc', 'createdAt_desc'] }
|
||||||
|
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unpin posts', () => {
|
||||||
|
const unpinPostMutation = gql`
|
||||||
|
mutation($id: ID!) {
|
||||||
|
unpinPost(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
content
|
||||||
|
author {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
pinnedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
beforeEach(async () => {
|
||||||
|
variables = { ...variables }
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
authenticatedUser = null
|
||||||
|
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { unpinPost: null },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('users cannot unpin posts', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { unpinPost: null },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('moderators cannot unpin posts', () => {
|
||||||
|
let moderator
|
||||||
|
beforeEach(async () => {
|
||||||
|
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
|
||||||
|
authenticatedUser = await moderator.toJson()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
data: { unpinPost: null },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('admin can unpin posts', () => {
|
||||||
|
let admin, pinnedPost
|
||||||
|
beforeEach(async () => {
|
||||||
|
pinnedPost = await factory.create('Post', { id: 'post-to-be-unpinned' })
|
||||||
|
admin = await user.update({
|
||||||
|
role: 'admin',
|
||||||
|
name: 'Admin',
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
authenticatedUser = await admin.toJson()
|
||||||
|
await admin.relateTo(pinnedPost, 'pinned', { createdAt: new Date().toISOString() })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('responds with the unpinned Post', async () => {
|
||||||
|
authenticatedUser = await admin.toJson()
|
||||||
|
variables = { ...variables, id: 'post-to-be-unpinned' }
|
||||||
|
const expected = {
|
||||||
|
data: {
|
||||||
|
unpinPost: {
|
||||||
|
id: 'post-to-be-unpinned',
|
||||||
|
pinnedBy: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('DeletePost', () => {
|
describe('DeletePost', () => {
|
||||||
|
|||||||
@ -1,25 +1,17 @@
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
|
||||||
import Factory from '../../seed/factories'
|
|
||||||
import { host, login, gql } from '../../jest/helpers'
|
|
||||||
import { getDriver, neode } from '../../bootstrap/neo4j'
|
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import createServer from '../.././server'
|
import createServer from '../.././server'
|
||||||
|
import Factory from '../../seed/factories'
|
||||||
|
import { gql } from '../../jest/helpers'
|
||||||
|
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
const instance = neode()
|
const instance = getNeode()
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
|
|
||||||
describe('report mutation', () => {
|
describe('report resources', () => {
|
||||||
let reportMutation
|
let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser
|
||||||
let headers
|
|
||||||
let client
|
|
||||||
let variables
|
|
||||||
let createPostVariables
|
|
||||||
let user
|
|
||||||
const categoryIds = ['cat9']
|
const categoryIds = ['cat9']
|
||||||
|
const reportMutation = gql`
|
||||||
const action = () => {
|
|
||||||
reportMutation = gql`
|
|
||||||
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||||
report(
|
report(
|
||||||
resourceId: $resourceId
|
resourceId: $resourceId
|
||||||
@ -45,27 +37,52 @@ describe('report mutation', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
client = new GraphQLClient(host, {
|
const variables = {
|
||||||
headers,
|
|
||||||
})
|
|
||||||
return client.request(reportMutation, variables)
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
variables = {
|
|
||||||
resourceId: 'whatever',
|
resourceId: 'whatever',
|
||||||
reasonCategory: 'other',
|
reasonCategory: 'other',
|
||||||
reasonDescription: 'Violates code of conduct !!!',
|
reasonDescription: 'Violates code of conduct !!!',
|
||||||
}
|
}
|
||||||
headers = {}
|
|
||||||
user = await factory.create('User', {
|
beforeAll(async () => {
|
||||||
id: 'u1',
|
await factory.cleanDatabase()
|
||||||
|
const { server } = createServer({
|
||||||
|
context: () => {
|
||||||
|
return {
|
||||||
|
driver,
|
||||||
|
neode: instance,
|
||||||
|
user: authenticatedUser,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mutate = createTestClient(server).mutate
|
||||||
|
query = createTestClient(server).query
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('report a resource', () => {
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
authenticatedUser = null
|
||||||
|
await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({
|
||||||
|
data: { report: null },
|
||||||
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
currentUser = await factory.create('User', {
|
||||||
|
id: 'current-user-id',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'test@example.org',
|
email: 'test@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
})
|
})
|
||||||
await factory.create('User', {
|
await factory.create('User', {
|
||||||
id: 'u2',
|
id: 'abusive-user-id',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
name: 'abusive-user',
|
name: 'abusive-user',
|
||||||
email: 'abusive-user@example.org',
|
email: 'abusive-user@example.org',
|
||||||
@ -75,122 +92,167 @@ describe('report mutation', () => {
|
|||||||
name: 'Democracy & Politics',
|
name: 'Democracy & Politics',
|
||||||
icon: 'university',
|
icon: 'university',
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
authenticatedUser = await currentUser.toJson()
|
||||||
await factory.cleanDatabase()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
it('throws authorization error', async () => {
|
|
||||||
await expect(action()).rejects.toThrow('Not Authorised')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
headers = await login({
|
|
||||||
email: 'test@example.org',
|
|
||||||
password: '1234',
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('invalid resource id', () => {
|
describe('invalid resource id', () => {
|
||||||
it('returns null', async () => {
|
it('returns null', async () => {
|
||||||
await expect(action()).resolves.toEqual({
|
await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({
|
||||||
report: null,
|
data: { report: null },
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('valid resource id', () => {
|
describe('valid resource', () => {
|
||||||
describe('reported resource is a user', () => {
|
describe('reported resource is a user', () => {
|
||||||
beforeEach(async () => {
|
|
||||||
variables = {
|
|
||||||
...variables,
|
|
||||||
resourceId: 'u2',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns type "User"', async () => {
|
it('returns type "User"', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
type: 'User',
|
type: 'User',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns resource in user attribute', async () => {
|
it('returns resource in user attribute', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
user: {
|
user: {
|
||||||
name: 'abusive-user',
|
name: 'abusive-user',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the submitter', async () => {
|
it('returns the submitter', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
submitter: {
|
submitter: {
|
||||||
email: 'test@example.org',
|
email: 'test@example.org',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns a date', async () => {
|
it('returns a date', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the reason category', async () => {
|
it('returns the reason category', async () => {
|
||||||
variables = {
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
...variables,
|
...variables,
|
||||||
|
resourceId: 'abusive-user-id',
|
||||||
reasonCategory: 'criminal_behavior_violation_german_law',
|
reasonCategory: 'criminal_behavior_violation_german_law',
|
||||||
}
|
},
|
||||||
await expect(action()).resolves.toMatchObject({
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
reasonCategory: 'criminal_behavior_violation_german_law',
|
reasonCategory: 'criminal_behavior_violation_german_law',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gives an error if the reason category is not in enum "ReasonCategory"', async () => {
|
it('gives an error if the reason category is not in enum "ReasonCategory"', async () => {
|
||||||
variables = {
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
...variables,
|
...variables,
|
||||||
reasonCategory: 'my_category',
|
resourceId: 'abusive-user-id',
|
||||||
}
|
reasonCategory: 'category_missing_from_enum_reason_category',
|
||||||
await expect(action()).rejects.toThrow(
|
},
|
||||||
'got invalid value "my_category"; Expected type ReasonCategory',
|
}),
|
||||||
)
|
).resolves.toMatchObject({
|
||||||
|
data: undefined,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
'Variable "$reasonCategory" got invalid value "category_missing_from_enum_reason_category"; Expected type ReasonCategory.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the reason description', async () => {
|
it('returns the reason description', async () => {
|
||||||
variables = {
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
...variables,
|
...variables,
|
||||||
|
resourceId: 'abusive-user-id',
|
||||||
reasonDescription: 'My reason!',
|
reasonDescription: 'My reason!',
|
||||||
}
|
},
|
||||||
await expect(action()).resolves.toMatchObject({
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
reasonDescription: 'My reason!',
|
reasonDescription: 'My reason!',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sanitize the reason description', async () => {
|
it('sanitize the reason description', async () => {
|
||||||
variables = {
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
...variables,
|
...variables,
|
||||||
|
resourceId: 'abusive-user-id',
|
||||||
reasonDescription: 'My reason <sanitize></sanitize>!',
|
reasonDescription: 'My reason <sanitize></sanitize>!',
|
||||||
}
|
},
|
||||||
await expect(action()).resolves.toMatchObject({
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
reasonDescription: 'My reason !',
|
reasonDescription: 'My reason !',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -198,48 +260,75 @@ describe('report mutation', () => {
|
|||||||
describe('reported resource is a post', () => {
|
describe('reported resource is a post', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('Post', {
|
await factory.create('Post', {
|
||||||
author: user,
|
author: currentUser,
|
||||||
id: 'p23',
|
id: 'post-to-report-id',
|
||||||
title: 'Matt and Robert having a pair-programming',
|
title: 'This is a post that is going to be reported',
|
||||||
categoryIds,
|
categoryIds,
|
||||||
})
|
})
|
||||||
variables = {
|
|
||||||
...variables,
|
|
||||||
resourceId: 'p23',
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns type "Post"', async () => {
|
it('returns type "Post"', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
resourceId: 'post-to-report-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
type: 'Post',
|
type: 'Post',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns resource in post attribute', async () => {
|
it('returns resource in post attribute', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
resourceId: 'post-to-report-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
post: {
|
post: {
|
||||||
title: 'Matt and Robert having a pair-programming',
|
title: 'This is a post that is going to be reported',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns null in user attribute', async () => {
|
it('returns null in user attribute', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
resourceId: 'post-to-report-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
user: null,
|
user: null,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/* An der Stelle würde ich den p23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
|
|
||||||
At this point I would check the p23 again, but this time there must be an error message. */
|
|
||||||
|
|
||||||
describe('reported resource is a comment', () => {
|
describe('reported resource is a comment', () => {
|
||||||
|
let createPostVariables
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
createPostVariables = {
|
createPostVariables = {
|
||||||
id: 'p1',
|
id: 'p1',
|
||||||
@ -247,80 +336,82 @@ describe('report mutation', () => {
|
|||||||
content: 'please comment on me',
|
content: 'please comment on me',
|
||||||
categoryIds,
|
categoryIds,
|
||||||
}
|
}
|
||||||
await factory.create('Post', { ...createPostVariables, author: user })
|
await factory.create('Post', { ...createPostVariables, author: currentUser })
|
||||||
await factory.create('Comment', {
|
await factory.create('Comment', {
|
||||||
author: user,
|
author: currentUser,
|
||||||
postId: 'p1',
|
postId: 'p1',
|
||||||
id: 'c34',
|
id: 'comment-to-report-id',
|
||||||
content: 'Robert getting tired.',
|
content: 'Post comment to be reported.',
|
||||||
})
|
})
|
||||||
variables = {
|
|
||||||
...variables,
|
|
||||||
resourceId: 'c34',
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns type "Comment"', async () => {
|
it('returns type "Comment"', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
resourceId: 'comment-to-report-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
type: 'Comment',
|
type: 'Comment',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns resource in comment attribute', async () => {
|
it('returns resource in comment attribute', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
resourceId: 'comment-to-report-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
report: {
|
report: {
|
||||||
comment: {
|
comment: {
|
||||||
content: 'Robert getting tired.',
|
content: 'Post comment to be reported.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/* An der Stelle würde ich den c34 noch mal prüfen, diesmal muss aber eine error meldung kommen.
|
|
||||||
At this point I would check the c34 again, but this time there must be an error message. */
|
|
||||||
|
|
||||||
describe('reported resource is a tag', () => {
|
describe('reported resource is a tag', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('Tag', {
|
await factory.create('Tag', {
|
||||||
id: 't23',
|
id: 'tag-to-report-id',
|
||||||
})
|
})
|
||||||
variables = {
|
|
||||||
...variables,
|
|
||||||
resourceId: 't23',
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns null', async () => {
|
it('returns null', async () => {
|
||||||
await expect(action()).resolves.toMatchObject({
|
await expect(
|
||||||
report: null,
|
mutate({
|
||||||
|
mutation: reportMutation,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
resourceId: 'tag-to-report-id',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: { report: null },
|
||||||
|
errors: undefined,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/* An der Stelle würde ich den t23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
|
|
||||||
At this point I would check the t23 again, but this time there must be an error message. */
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('query for reported resource', () => {
|
||||||
describe('reports query', () => {
|
|
||||||
let query, mutate, authenticatedUser, moderator, user, author
|
|
||||||
const categoryIds = ['cat9']
|
|
||||||
|
|
||||||
const reportMutation = gql`
|
|
||||||
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
|
||||||
report(
|
|
||||||
resourceId: $resourceId
|
|
||||||
reasonCategory: $reasonCategory
|
|
||||||
reasonDescription: $reasonDescription
|
|
||||||
) {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const reportsQuery = gql`
|
const reportsQuery = gql`
|
||||||
query {
|
query {
|
||||||
reports(orderBy: createdAt_desc) {
|
reports(orderBy: createdAt_desc) {
|
||||||
@ -344,37 +435,23 @@ describe('reports query', () => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await factory.cleanDatabase()
|
|
||||||
const { server } = createServer({
|
|
||||||
context: () => {
|
|
||||||
return {
|
|
||||||
driver,
|
|
||||||
user: authenticatedUser,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
query = createTestClient(server).query
|
|
||||||
mutate = createTestClient(server).mutate
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
authenticatedUser = null
|
authenticatedUser = null
|
||||||
|
|
||||||
moderator = await factory.create('User', {
|
moderator = await factory.create('User', {
|
||||||
id: 'mod1',
|
id: 'moderator-1',
|
||||||
role: 'moderator',
|
role: 'moderator',
|
||||||
email: 'moderator@example.org',
|
email: 'moderator@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
})
|
})
|
||||||
user = await factory.create('User', {
|
currentUser = await factory.create('User', {
|
||||||
id: 'user1',
|
id: 'current-user-id',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'test@example.org',
|
email: 'current.user@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
})
|
})
|
||||||
author = await factory.create('User', {
|
abusiveUser = await factory.create('User', {
|
||||||
id: 'auth1',
|
id: 'abusive-user-1',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
name: 'abusive-user',
|
name: 'abusive-user',
|
||||||
email: 'abusive-user@example.org',
|
email: 'abusive-user@example.org',
|
||||||
@ -387,38 +464,37 @@ describe('reports query', () => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
factory.create('Post', {
|
factory.create('Post', {
|
||||||
author,
|
author: abusiveUser,
|
||||||
id: 'p1',
|
id: 'abusive-post-1',
|
||||||
categoryIds,
|
categoryIds,
|
||||||
content: 'Interesting Knowledge',
|
content: 'Interesting Knowledge',
|
||||||
}),
|
}),
|
||||||
factory.create('Post', {
|
factory.create('Post', {
|
||||||
author: moderator,
|
author: moderator,
|
||||||
id: 'p2',
|
id: 'post-2',
|
||||||
categoryIds,
|
categoryIds,
|
||||||
content: 'More things to do …',
|
content: 'More things to do …',
|
||||||
}),
|
}),
|
||||||
factory.create('Post', {
|
factory.create('Post', {
|
||||||
author: user,
|
author: currentUser,
|
||||||
id: 'p3',
|
id: 'post-3',
|
||||||
categoryIds,
|
categoryIds,
|
||||||
content: 'I am at school …',
|
content: 'I am at school …',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
factory.create('Comment', {
|
factory.create('Comment', {
|
||||||
author: user,
|
author: currentUser,
|
||||||
id: 'c1',
|
id: 'abusive-comment-1',
|
||||||
postId: 'p1',
|
postId: 'post-1',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
authenticatedUser = await currentUser.toJson()
|
||||||
authenticatedUser = await user.toJson()
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
mutate({
|
mutate({
|
||||||
mutation: reportMutation,
|
mutation: reportMutation,
|
||||||
variables: {
|
variables: {
|
||||||
resourceId: 'p1',
|
resourceId: 'abusive-post-1',
|
||||||
reasonCategory: 'other',
|
reasonCategory: 'other',
|
||||||
reasonDescription: 'This comment is bigoted',
|
reasonDescription: 'This comment is bigoted',
|
||||||
},
|
},
|
||||||
@ -426,7 +502,7 @@ describe('reports query', () => {
|
|||||||
mutate({
|
mutate({
|
||||||
mutation: reportMutation,
|
mutation: reportMutation,
|
||||||
variables: {
|
variables: {
|
||||||
resourceId: 'c1',
|
resourceId: 'abusive-comment-1',
|
||||||
reasonCategory: 'discrimination_etc',
|
reasonCategory: 'discrimination_etc',
|
||||||
reasonDescription: 'This post is bigoted',
|
reasonDescription: 'This post is bigoted',
|
||||||
},
|
},
|
||||||
@ -434,7 +510,7 @@ describe('reports query', () => {
|
|||||||
mutate({
|
mutate({
|
||||||
mutation: reportMutation,
|
mutation: reportMutation,
|
||||||
variables: {
|
variables: {
|
||||||
resourceId: 'auth1',
|
resourceId: 'abusive-user-1',
|
||||||
reasonCategory: 'doxing',
|
reasonCategory: 'doxing',
|
||||||
reasonDescription: 'This user is harassing me with bigoted remarks',
|
reasonDescription: 'This user is harassing me with bigoted remarks',
|
||||||
},
|
},
|
||||||
@ -442,11 +518,6 @@ describe('reports query', () => {
|
|||||||
])
|
])
|
||||||
authenticatedUser = null
|
authenticatedUser = null
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await factory.cleanDatabase()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
authenticatedUser = null
|
authenticatedUser = null
|
||||||
@ -455,9 +526,10 @@ describe('reports query', () => {
|
|||||||
errors: [{ message: 'Not Authorised!' }],
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
describe('authenticated', () => {
|
||||||
it('role "user" gets no reports', async () => {
|
it('role "user" gets no reports', async () => {
|
||||||
authenticatedUser = await user.toJson()
|
authenticatedUser = await currentUser.toJson()
|
||||||
expect(query({ query: reportsQuery })).resolves.toMatchObject({
|
expect(query({ query: reportsQuery })).resolves.toMatchObject({
|
||||||
data: { reports: null },
|
data: { reports: null },
|
||||||
errors: [{ message: 'Not Authorised!' }],
|
errors: [{ message: 'Not Authorised!' }],
|
||||||
@ -473,11 +545,11 @@ describe('reports query', () => {
|
|||||||
reasonCategory: 'doxing',
|
reasonCategory: 'doxing',
|
||||||
reasonDescription: 'This user is harassing me with bigoted remarks',
|
reasonDescription: 'This user is harassing me with bigoted remarks',
|
||||||
submitter: expect.objectContaining({
|
submitter: expect.objectContaining({
|
||||||
id: 'user1',
|
id: 'current-user-id',
|
||||||
}),
|
}),
|
||||||
type: 'User',
|
type: 'User',
|
||||||
user: expect.objectContaining({
|
user: expect.objectContaining({
|
||||||
id: 'auth1',
|
id: 'abusive-user-1',
|
||||||
}),
|
}),
|
||||||
post: null,
|
post: null,
|
||||||
comment: null,
|
comment: null,
|
||||||
@ -487,12 +559,12 @@ describe('reports query', () => {
|
|||||||
reasonCategory: 'other',
|
reasonCategory: 'other',
|
||||||
reasonDescription: 'This comment is bigoted',
|
reasonDescription: 'This comment is bigoted',
|
||||||
submitter: expect.objectContaining({
|
submitter: expect.objectContaining({
|
||||||
id: 'user1',
|
id: 'current-user-id',
|
||||||
}),
|
}),
|
||||||
type: 'Post',
|
type: 'Post',
|
||||||
user: null,
|
user: null,
|
||||||
post: expect.objectContaining({
|
post: expect.objectContaining({
|
||||||
id: 'p1',
|
id: 'abusive-post-1',
|
||||||
}),
|
}),
|
||||||
comment: null,
|
comment: null,
|
||||||
}),
|
}),
|
||||||
@ -501,21 +573,21 @@ describe('reports query', () => {
|
|||||||
reasonCategory: 'discrimination_etc',
|
reasonCategory: 'discrimination_etc',
|
||||||
reasonDescription: 'This post is bigoted',
|
reasonDescription: 'This post is bigoted',
|
||||||
submitter: expect.objectContaining({
|
submitter: expect.objectContaining({
|
||||||
id: 'user1',
|
id: 'current-user-id',
|
||||||
}),
|
}),
|
||||||
type: 'Comment',
|
type: 'Comment',
|
||||||
user: null,
|
user: null,
|
||||||
post: null,
|
post: null,
|
||||||
comment: expect.objectContaining({
|
comment: expect.objectContaining({
|
||||||
id: 'c1',
|
id: 'abusive-comment-1',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticatedUser = await moderator.toJson()
|
authenticatedUser = await moderator.toJson()
|
||||||
const { data } = await query({ query: reportsQuery })
|
const { data } = await query({ query: reportsQuery })
|
||||||
expect(data).toEqual(expected)
|
expect(data).toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export default {
|
|||||||
const transactionRes = await session.run(
|
const transactionRes = await session.run(
|
||||||
`MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
|
`MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
|
||||||
WHERE $type IN labels(node) AND NOT userWritten.id = $userId
|
WHERE $type IN labels(node) AND NOT userWritten.id = $userId
|
||||||
MERGE (user)-[relation:SHOUTED]->(node)
|
MERGE (user)-[relation:SHOUTED{createdAt:toString(datetime())}]->(node)
|
||||||
RETURN COUNT(relation) > 0 as isShouted`,
|
RETURN COUNT(relation) > 0 as isShouted`,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
|
|||||||
@ -102,6 +102,22 @@ describe('shout and unshout posts', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('adds `createdAt` to `SHOUT` relationship', async () => {
|
||||||
|
variables = { id: 'another-user-post-id' }
|
||||||
|
await mutate({ mutation: mutationShoutPost, variables })
|
||||||
|
const relation = await instance.cypher(
|
||||||
|
'MATCH (user:User {id: $userId1})-[relationship:SHOUTED]->(node {id: $userId2}) WHERE relationship.createdAt IS NOT NULL RETURN relationship',
|
||||||
|
{
|
||||||
|
userId1: 'current-user-id',
|
||||||
|
userId2: 'another-user-post-id',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const relationshipProperties = relation.records.map(
|
||||||
|
record => record.get('relationship').properties.createdAt,
|
||||||
|
)
|
||||||
|
expect(relationshipProperties[0]).toEqual(expect.any(String))
|
||||||
|
})
|
||||||
|
|
||||||
it('can not shout my own post', async () => {
|
it('can not shout my own post', async () => {
|
||||||
variables = { id: 'current-user-post-id' }
|
variables = { id: 'current-user-post-id' }
|
||||||
await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({
|
await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({
|
||||||
|
|||||||
@ -16,6 +16,10 @@ type Post {
|
|||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
language: String
|
language: String
|
||||||
|
pinnedAt: String @cypher(
|
||||||
|
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt"
|
||||||
|
)
|
||||||
|
pinnedBy: User @relation(name:"PINNED", direction: "IN")
|
||||||
relatedContributions: [Post]!
|
relatedContributions: [Post]!
|
||||||
@cypher(
|
@cypher(
|
||||||
statement: """
|
statement: """
|
||||||
@ -84,9 +88,12 @@ type Mutation {
|
|||||||
DeletePost(id: ID!): Post
|
DeletePost(id: ID!): Post
|
||||||
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||||
RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||||
|
pinPost(id: ID!): Post
|
||||||
|
unpinPost(id: ID!): Post
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
||||||
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
||||||
|
profilePagePosts(filter: _PostFilter, first: Int, offset: Int, orderBy: [_PostOrdering]): [Post]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,7 @@ type User {
|
|||||||
termsAndConditionsAgreedAt: String
|
termsAndConditionsAgreedAt: String
|
||||||
|
|
||||||
allowEmbedIframes: Boolean
|
allowEmbedIframes: Boolean
|
||||||
|
|
||||||
locale: String
|
locale: String
|
||||||
|
|
||||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
|
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||||
|
|
||||||
@ -123,6 +121,7 @@ input _UserFilter {
|
|||||||
followedBy_none: _UserFilter
|
followedBy_none: _UserFilter
|
||||||
followedBy_single: _UserFilter
|
followedBy_single: _UserFilter
|
||||||
followedBy_every: _UserFilter
|
followedBy_every: _UserFilter
|
||||||
|
role_in: [UserGroup!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
|||||||
@ -2782,10 +2782,10 @@ data-urls@^1.0.0:
|
|||||||
whatwg-mimetype "^2.2.0"
|
whatwg-mimetype "^2.2.0"
|
||||||
whatwg-url "^7.0.0"
|
whatwg-url "^7.0.0"
|
||||||
|
|
||||||
date-fns@2.5.0:
|
date-fns@2.5.1:
|
||||||
version "2.5.0"
|
version "2.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.5.0.tgz#b939f17c2902ce81cffe449702ba22c0781b38ec"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.5.1.tgz#6bd76f01d3a438e9c481d4c18512ddac37585b4c"
|
||||||
integrity sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==
|
integrity sha512-ZBrQmuaqH9YqIejbgu8f09ki7wdD2JxWsRTZ/+HnnLNmkI56ty0evnWzKY+ihLT0xX5VdUX0vDNZCxJJGKX2+Q==
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
@ -5802,14 +5802,14 @@ metascraper-publisher@^5.7.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.7.6"
|
"@metascraper/helpers" "^5.7.6"
|
||||||
|
|
||||||
metascraper-soundcloud@^5.7.6:
|
metascraper-soundcloud@^5.7.7:
|
||||||
version "5.7.6"
|
version "5.7.7"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.7.6.tgz#80c725e8746d94c992b5bdd07ac6bd987d09944d"
|
resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.7.7.tgz#b7cb56e5ce7744aed90c83e37303e9b40ad3ff9d"
|
||||||
integrity sha512-fBxX5mYPFf8rWhhEX2XZD5QrmvtUI5IIPzryGuwEWsbPuMGuUkvFA9JjHJiC46uYXoi6UuKLXwSmYHcAACG3Jg==
|
integrity sha512-TDJxUwFJCxU4bTrrx3GWiGeZdNhvRhlI61JiprLkYBriM65uzCfaJ5FjS5uzZy1CfMYhvQgxLZ7XRq1bgbPpTg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@metascraper/helpers" "^5.7.6"
|
"@metascraper/helpers" "^5.7.6"
|
||||||
memoize-one "~5.1.1"
|
memoize-one "~5.1.1"
|
||||||
tldts "~5.5.0"
|
tldts "~5.6.1"
|
||||||
|
|
||||||
metascraper-title@^5.7.6:
|
metascraper-title@^5.7.6:
|
||||||
version "5.7.6"
|
version "5.7.6"
|
||||||
@ -8026,17 +8026,17 @@ tlds@^1.187.0, tlds@^1.203.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.203.1.tgz#4dc9b02f53de3315bc98b80665e13de3edfc1dfc"
|
resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.203.1.tgz#4dc9b02f53de3315bc98b80665e13de3edfc1dfc"
|
||||||
integrity sha512-7MUlYyGJ6rSitEZ3r1Q1QNV8uSIzapS8SmmhSusBuIc7uIxPPwsKllEP0GRp1NS6Ik6F+fRZvnjDWm3ecv2hDw==
|
integrity sha512-7MUlYyGJ6rSitEZ3r1Q1QNV8uSIzapS8SmmhSusBuIc7uIxPPwsKllEP0GRp1NS6Ik6F+fRZvnjDWm3ecv2hDw==
|
||||||
|
|
||||||
tldts-core@^5.5.0:
|
tldts-core@^5.6.1:
|
||||||
version "5.5.0"
|
version "5.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.5.0.tgz#ae22afe586541ac5ecacc520038068639b3420b4"
|
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.6.1.tgz#943fd020b564018fae308c12ec2435e53101c257"
|
||||||
integrity sha512-o0JzahqioihXz8wj7/1OYtefyhXz/PwLno7VRm5MTwQitEOPpvMPZpj2yjXtjgOMKbi3A5OHvvJwhFf0Hutzng==
|
integrity sha512-ikhUCHoiRu0QzQpba0f0q1Km5YBnn4qsBzGlYCzT3y3wSCGG2GlV0xeEOcXTzp2pRne6bQaHRry4TINMZpDFKQ==
|
||||||
|
|
||||||
tldts@~5.5.0:
|
tldts@~5.6.1:
|
||||||
version "5.5.0"
|
version "5.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.5.0.tgz#12ea124593bc5abebd12107c6223986f97972bc1"
|
resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.6.1.tgz#36f4ac97505b9202f2872f6246f326589f49d78b"
|
||||||
integrity sha512-CZ/d7Y4k8onxwerMWz/mTCeKJtX3VAMiL+ajXVFnxsKhH4BV+QavjnZ1Mb9OeCHo3jX0S3Dw6ERNRXqOMVsDvw==
|
integrity sha512-I+imSP592J9GUYApIoiDdJk3KlroHY4zmDmpAp+TlIDZZAPxx192yOUViMB2QmlcRtZUz5XLEM3cS2F0V7P1Fw==
|
||||||
dependencies:
|
dependencies:
|
||||||
tldts-core "^5.5.0"
|
tldts-core "^5.6.1"
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM neo4j:3.5.11-enterprise
|
FROM neo4j:3.5.12-enterprise
|
||||||
LABEL Description="Neo4J database of the Social Network Human-Connection.org with preinstalled database constraints and indices" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
|
LABEL Description="Neo4J database of the Social Network Human-Connection.org with preinstalled database constraints and indices" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
|
||||||
|
|
||||||
ARG BUILD_COMMIT
|
ARG BUILD_COMMIT
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"cypress": "^3.4.1",
|
"cypress": "^3.4.1",
|
||||||
"cypress-cucumber-preprocessor": "^1.16.2",
|
"cypress-cucumber-preprocessor": "^1.16.2",
|
||||||
"cypress-file-upload": "^3.3.4",
|
"cypress-file-upload": "^3.4.0",
|
||||||
"cypress-plugin-retries": "^1.3.0",
|
"cypress-plugin-retries": "^1.3.0",
|
||||||
"date-fns": "^2.5.0",
|
"date-fns": "^2.5.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
|
|||||||
@ -149,6 +149,7 @@ export default {
|
|||||||
},
|
},
|
||||||
editCommentMenu(showMenu) {
|
editCommentMenu(showMenu) {
|
||||||
this.openEditCommentMenu = showMenu
|
this.openEditCommentMenu = showMenu
|
||||||
|
this.$emit('toggleNewCommentForm', !showMenu)
|
||||||
},
|
},
|
||||||
updateComment(comment) {
|
updateComment(comment) {
|
||||||
this.$emit('updateComment', comment)
|
this.$emit('updateComment', comment)
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
:routeHash="routeHash"
|
:routeHash="routeHash"
|
||||||
@deleteComment="updateCommentList"
|
@deleteComment="updateCommentList"
|
||||||
@updateComment="updateCommentList"
|
@updateComment="updateCommentList"
|
||||||
|
@toggleNewCommentForm="toggleNewCommentForm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -51,6 +52,9 @@ export default {
|
|||||||
return comment.id === updatedComment.id ? updatedComment : comment
|
return comment.id === updatedComment.id ? updatedComment : comment
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
toggleNewCommentForm(showNewCommentForm) {
|
||||||
|
this.$emit('toggleNewCommentForm', showNewCommentForm)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -55,7 +55,8 @@ export default {
|
|||||||
routes() {
|
routes() {
|
||||||
let routes = []
|
let routes = []
|
||||||
|
|
||||||
if (this.isOwner && this.resourceType === 'contribution') {
|
if (this.resourceType === 'contribution') {
|
||||||
|
if (this.isOwner) {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`post.menu.edit`),
|
name: this.$t(`post.menu.edit`),
|
||||||
path: this.$router.resolve({
|
path: this.$router.resolve({
|
||||||
@ -75,6 +76,27 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isAdmin) {
|
||||||
|
if (!this.resource.pinnedBy) {
|
||||||
|
routes.push({
|
||||||
|
name: this.$t(`post.menu.pin`),
|
||||||
|
callback: () => {
|
||||||
|
this.$emit('pinPost', this.resource)
|
||||||
|
},
|
||||||
|
icon: 'link',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
routes.push({
|
||||||
|
name: this.$t(`post.menu.unpin`),
|
||||||
|
callback: () => {
|
||||||
|
this.$emit('unpinPost', this.resource)
|
||||||
|
},
|
||||||
|
icon: 'unlink',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isOwner && this.resourceType === 'comment') {
|
if (this.isOwner && this.resourceType === 'comment') {
|
||||||
routes.push({
|
routes.push({
|
||||||
name: this.$t(`comment.menu.edit`),
|
name: this.$t(`comment.menu.edit`),
|
||||||
@ -155,6 +177,9 @@ export default {
|
|||||||
isModerator() {
|
isModerator() {
|
||||||
return this.$store.getters['auth/isModerator']
|
return this.$store.getters['auth/isModerator']
|
||||||
},
|
},
|
||||||
|
isAdmin() {
|
||||||
|
return this.$store.getters['auth/isAdmin']
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openItem(route, toggleMenu) {
|
openItem(route, toggleMenu) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { config, shallowMount, mount, createLocalVue, RouterLinkStub } from '@vu
|
|||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Filters from '~/plugins/vue-filters'
|
import Filters from '~/plugins/vue-filters'
|
||||||
import PostCard from '.'
|
import PostCard from './PostCard.vue'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { storiesOf } from '@storybook/vue'
|
import { storiesOf } from '@storybook/vue'
|
||||||
import { withA11y } from '@storybook/addon-a11y'
|
import { withA11y } from '@storybook/addon-a11y'
|
||||||
import HcPostCard from '~/components/PostCard'
|
import HcPostCard from './PostCard.vue'
|
||||||
import helpers from '~/storybook/helpers'
|
import helpers from '~/storybook/helpers'
|
||||||
|
|
||||||
helpers.init()
|
helpers.init()
|
||||||
@ -76,3 +76,23 @@ storiesOf('Post Card', module)
|
|||||||
/>
|
/>
|
||||||
`,
|
`,
|
||||||
}))
|
}))
|
||||||
|
.add('pinned by admin', () => ({
|
||||||
|
components: { HcPostCard },
|
||||||
|
store: helpers.store,
|
||||||
|
data: () => ({
|
||||||
|
post: {
|
||||||
|
...post,
|
||||||
|
pinnedBy: {
|
||||||
|
id: '4711',
|
||||||
|
name: 'Ad Min',
|
||||||
|
role: 'admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
template: `
|
||||||
|
<hc-post-card
|
||||||
|
:post="post"
|
||||||
|
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
|
||||||
|
/>
|
||||||
|
`,
|
||||||
|
}))
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-card
|
<ds-card
|
||||||
:image="post.image | proxyApiUrl"
|
:image="post.image | proxyApiUrl"
|
||||||
:class="{ 'post-card': true, 'disabled-content': post.disabled }"
|
:class="{ 'post-card': true, 'disabled-content': post.disabled, 'post--pinned': isPinned }"
|
||||||
>
|
>
|
||||||
<!-- Post Link Target -->
|
<!-- Post Link Target -->
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
@ -16,7 +16,8 @@
|
|||||||
<client-only>
|
<client-only>
|
||||||
<hc-user :user="post.author" :trunc="35" :date-time="post.createdAt" />
|
<hc-user :user="post.author" :trunc="35" :date-time="post.createdAt" />
|
||||||
</client-only>
|
</client-only>
|
||||||
<hc-ribbon :text="$t('post.name')" />
|
<hc-ribbon v-if="isPinned" class="ribbon--pinned" :text="$t('post.pinned')" />
|
||||||
|
<hc-ribbon v-else :text="$t('post.name')" />
|
||||||
</div>
|
</div>
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
<!-- Post Title -->
|
<!-- Post Title -->
|
||||||
@ -61,6 +62,8 @@
|
|||||||
:resource="post"
|
:resource="post"
|
||||||
:modalsData="menuModalsData"
|
:modalsData="menuModalsData"
|
||||||
:is-owner="isAuthor"
|
:is-owner="isAuthor"
|
||||||
|
@pinPost="pinPost"
|
||||||
|
@unpinPost="unpinPost"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</client-only>
|
</client-only>
|
||||||
@ -114,6 +117,9 @@ export default {
|
|||||||
this.deletePostCallback,
|
this.deletePostCallback,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
isPinned() {
|
||||||
|
return this.post && this.post.pinnedBy
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async deletePostCallback() {
|
async deletePostCallback() {
|
||||||
@ -127,6 +133,12 @@ export default {
|
|||||||
this.$toast.error(err.message)
|
this.$toast.error(err.message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pinPost(post) {
|
||||||
|
this.$emit('pinPost', post)
|
||||||
|
},
|
||||||
|
unpinPost(post) {
|
||||||
|
this.$emit('unpinPost', post)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -167,4 +179,8 @@ export default {
|
|||||||
text-indent: -999999px;
|
text-indent: -999999px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post--pinned {
|
||||||
|
border: 1px solid $color-warning;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -46,4 +46,12 @@ export default {
|
|||||||
border-color: $background-color-secondary transparent transparent $background-color-secondary;
|
border-color: $background-color-secondary transparent transparent $background-color-secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ribbon--pinned {
|
||||||
|
background-color: $color-warning-active;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-color: $color-warning transparent transparent $color-warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export default {
|
|||||||
return data.notifications
|
return data.notifications
|
||||||
},
|
},
|
||||||
error(error) {
|
error(error) {
|
||||||
this.$toast.error(error)
|
this.$toast.error(error.message)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -57,6 +57,12 @@ export const postFragment = lang => gql`
|
|||||||
name
|
name
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
pinnedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
}
|
||||||
|
pinnedAt
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
export const commentFragment = lang => gql`
|
export const commentFragment = lang => gql`
|
||||||
|
|||||||
@ -50,6 +50,11 @@ export default () => {
|
|||||||
content
|
content
|
||||||
contentExcerpt
|
contentExcerpt
|
||||||
language
|
language
|
||||||
|
pinnedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -86,5 +91,39 @@ export default () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
pinPost: gql`
|
||||||
|
mutation($id: ID!) {
|
||||||
|
pinPost(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
content
|
||||||
|
contentExcerpt
|
||||||
|
language
|
||||||
|
pinnedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
unpinPost: gql`
|
||||||
|
mutation($id: ID!) {
|
||||||
|
unpinPost(id: $id) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
content
|
||||||
|
contentExcerpt
|
||||||
|
language
|
||||||
|
pinnedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,26 @@ export const filterPosts = i18n => {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const profilePagePosts = i18n => {
|
||||||
|
const lang = i18n.locale().toUpperCase()
|
||||||
|
return gql`
|
||||||
|
${postFragment(lang)}
|
||||||
|
${postCountsFragment}
|
||||||
|
|
||||||
|
query profilePagePosts(
|
||||||
|
$filter: _PostFilter
|
||||||
|
$first: Int
|
||||||
|
$offset: Int
|
||||||
|
$orderBy: [_PostOrdering]
|
||||||
|
) {
|
||||||
|
profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) {
|
||||||
|
...post
|
||||||
|
...postCounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
export const PostsEmotionsByCurrentUser = () => {
|
export const PostsEmotionsByCurrentUser = () => {
|
||||||
return gql`
|
return gql`
|
||||||
query PostsEmotionsByCurrentUser($postId: ID!) {
|
query PostsEmotionsByCurrentUser($postId: ID!) {
|
||||||
|
|||||||
@ -362,6 +362,7 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"name": "Beitrag",
|
"name": "Beitrag",
|
||||||
|
"pinned": "Meldung",
|
||||||
"moreInfo": {
|
"moreInfo": {
|
||||||
"name": "Mehr Info",
|
"name": "Mehr Info",
|
||||||
"title": "Mehr Informationen",
|
"title": "Mehr Informationen",
|
||||||
@ -375,7 +376,11 @@
|
|||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"edit": "Beitrag bearbeiten",
|
"edit": "Beitrag bearbeiten",
|
||||||
"delete": "Beitrag löschen"
|
"delete": "Beitrag löschen",
|
||||||
|
"pin": "Post festpinnen",
|
||||||
|
"pinnedSuccessfully": "Post erfolgreich festgepinnt!",
|
||||||
|
"unpin": "Post nicht mehr festpinnen",
|
||||||
|
"unpinnedSuccessfully": "Post erfolgreich nicht mehr festgepinnt!"
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"submit": "Kommentiere",
|
"submit": "Kommentiere",
|
||||||
|
|||||||
@ -363,6 +363,7 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"name": "Post",
|
"name": "Post",
|
||||||
|
"pinned": "Announcement",
|
||||||
"moreInfo": {
|
"moreInfo": {
|
||||||
"name": "More info",
|
"name": "More info",
|
||||||
"title": "More information",
|
"title": "More information",
|
||||||
@ -376,7 +377,11 @@
|
|||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"edit": "Edit Post",
|
"edit": "Edit Post",
|
||||||
"delete": "Delete Post"
|
"delete": "Delete Post",
|
||||||
|
"pin": "Pin post",
|
||||||
|
"pinnedSuccessfully": "Post pinned successfully!",
|
||||||
|
"unpin": "Unpin post",
|
||||||
|
"unpinnedSuccessfully": "Post unpinned successfully!"
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"submit": "Comment",
|
"submit": "Comment",
|
||||||
|
|||||||
@ -79,7 +79,7 @@
|
|||||||
"string-hash": "^1.1.3",
|
"string-hash": "^1.1.3",
|
||||||
"tippy.js": "^4.3.5",
|
"tippy.js": "^4.3.5",
|
||||||
"tiptap": "~1.26.3",
|
"tiptap": "~1.26.3",
|
||||||
"tiptap-extensions": "~1.28.3",
|
"tiptap-extensions": "~1.28.4",
|
||||||
"trunc-html": "^1.1.2",
|
"trunc-html": "^1.1.2",
|
||||||
"v-tooltip": "~2.0.2",
|
"v-tooltip": "~2.0.2",
|
||||||
"vue-count-to": "~1.0.13",
|
"vue-count-to": "~1.0.13",
|
||||||
@ -98,7 +98,7 @@
|
|||||||
"@storybook/addon-a11y": "^5.2.4",
|
"@storybook/addon-a11y": "^5.2.4",
|
||||||
"@storybook/addon-actions": "^5.2.4",
|
"@storybook/addon-actions": "^5.2.4",
|
||||||
"@storybook/vue": "~5.2.4",
|
"@storybook/vue": "~5.2.4",
|
||||||
"@vue/cli-shared-utils": "~3.12.0",
|
"@vue/cli-shared-utils": "~4.0.4",
|
||||||
"@vue/eslint-config-prettier": "~5.0.0",
|
"@vue/eslint-config-prettier": "~5.0.0",
|
||||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||||
"@vue/test-utils": "~1.0.0-beta.29",
|
"@vue/test-utils": "~1.0.0-beta.29",
|
||||||
|
|||||||
@ -20,6 +20,8 @@
|
|||||||
:post="post"
|
:post="post"
|
||||||
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
|
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
|
||||||
@removePostFromList="deletePost"
|
@removePostFromList="deletePost"
|
||||||
|
@pinPost="pinPost"
|
||||||
|
@unpinPost="unpinPost"
|
||||||
/>
|
/>
|
||||||
</masonry-grid-item>
|
</masonry-grid-item>
|
||||||
</template>
|
</template>
|
||||||
@ -57,12 +59,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||||
import HcEmpty from '~/components/Empty'
|
import HcEmpty from '~/components/Empty'
|
||||||
import HcPostCard from '~/components/PostCard'
|
import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||||
import HcLoadMore from '~/components/LoadMore.vue'
|
import HcLoadMore from '~/components/LoadMore.vue'
|
||||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
import { filterPosts } from '~/graphql/PostQuery.js'
|
import { filterPosts } from '~/graphql/PostQuery.js'
|
||||||
|
import PostMutations from '~/graphql/PostMutations'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -161,6 +164,37 @@ export default {
|
|||||||
return post.id !== deletedPost.id
|
return post.id !== deletedPost.id
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
resetPostList() {
|
||||||
|
this.offset = 0
|
||||||
|
this.posts = []
|
||||||
|
this.hasMore = true
|
||||||
|
},
|
||||||
|
pinPost(post) {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: PostMutations().pinPost,
|
||||||
|
variables: { id: post.id },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('post.menu.pinnedSuccessfully'))
|
||||||
|
this.resetPostList()
|
||||||
|
this.$apollo.queries.Post.refetch()
|
||||||
|
})
|
||||||
|
.catch(error => this.$toast.error(error.message))
|
||||||
|
},
|
||||||
|
unpinPost(post) {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: PostMutations().unpinPost,
|
||||||
|
variables: { id: post.id },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('post.menu.unpinnedSuccessfully'))
|
||||||
|
this.resetPostList()
|
||||||
|
this.$apollo.queries.Post.refetch()
|
||||||
|
})
|
||||||
|
.catch(error => this.$toast.error(error.message))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
Post: {
|
Post: {
|
||||||
@ -171,7 +205,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
filter: this.finalFilters,
|
filter: this.finalFilters,
|
||||||
first: this.pageSize,
|
first: this.pageSize,
|
||||||
orderBy: this.orderBy,
|
orderBy: ['pinnedAt_asc', this.orderBy],
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -31,6 +31,9 @@ describe('PostSlug', () => {
|
|||||||
$filters: {
|
$filters: {
|
||||||
truncate: a => a,
|
truncate: a => a,
|
||||||
},
|
},
|
||||||
|
$route: {
|
||||||
|
hash: '',
|
||||||
|
},
|
||||||
// If you are mocking the router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
// If you are mocking the router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
||||||
$router: {
|
$router: {
|
||||||
history: {
|
history: {
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
:resource="post"
|
:resource="post"
|
||||||
:modalsData="menuModalsData"
|
:modalsData="menuModalsData"
|
||||||
:is-owner="isAuthor(post.author ? post.author.id : null)"
|
:is-owner="isAuthor(post.author ? post.author.id : null)"
|
||||||
|
@pinPost="pinPost"
|
||||||
|
@unpinPost="unpinPost"
|
||||||
/>
|
/>
|
||||||
</client-only>
|
</client-only>
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
@ -68,9 +70,13 @@
|
|||||||
</ds-space>
|
</ds-space>
|
||||||
<!-- Comments -->
|
<!-- Comments -->
|
||||||
<ds-section slot="footer">
|
<ds-section slot="footer">
|
||||||
<hc-comment-list :post="post" :routeHash="$route.hash" />
|
<hc-comment-list
|
||||||
|
:post="post"
|
||||||
|
:routeHash="$route.hash"
|
||||||
|
@toggleNewCommentForm="toggleNewCommentForm"
|
||||||
|
/>
|
||||||
<ds-space margin-bottom="large" />
|
<ds-space margin-bottom="large" />
|
||||||
<hc-comment-form :post="post" @createComment="createComment" />
|
<hc-comment-form v-if="showNewCommentForm" :post="post" @createComment="createComment" />
|
||||||
</ds-section>
|
</ds-section>
|
||||||
</ds-card>
|
</ds-card>
|
||||||
</transition>
|
</transition>
|
||||||
@ -88,6 +94,7 @@ import HcCommentList from '~/components/CommentList/CommentList'
|
|||||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||||
import PostQuery from '~/graphql/PostQuery'
|
import PostQuery from '~/graphql/PostQuery'
|
||||||
import HcEmotions from '~/components/Emotions/Emotions'
|
import HcEmotions from '~/components/Emotions/Emotions'
|
||||||
|
import PostMutations from '~/graphql/PostMutations'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PostSlug',
|
name: 'PostSlug',
|
||||||
@ -116,6 +123,7 @@ export default {
|
|||||||
post: null,
|
post: null,
|
||||||
ready: false,
|
ready: false,
|
||||||
title: 'loading',
|
title: 'loading',
|
||||||
|
showNewCommentForm: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -156,6 +164,31 @@ export default {
|
|||||||
async createComment(comment) {
|
async createComment(comment) {
|
||||||
this.post.comments.push(comment)
|
this.post.comments.push(comment)
|
||||||
},
|
},
|
||||||
|
pinPost(post) {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: PostMutations().pinPost,
|
||||||
|
variables: { id: post.id },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('post.menu.pinnedSuccessfully'))
|
||||||
|
})
|
||||||
|
.catch(error => this.$toast.error(error.message))
|
||||||
|
},
|
||||||
|
unpinPost(post) {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: PostMutations().unpinPost,
|
||||||
|
variables: { id: post.id },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('post.menu.unpinnedSuccessfully'))
|
||||||
|
})
|
||||||
|
.catch(error => this.$toast.error(error.message))
|
||||||
|
},
|
||||||
|
toggleNewCommentForm(showNewCommentForm) {
|
||||||
|
this.showNewCommentForm = showNewCommentForm
|
||||||
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
Post: {
|
Post: {
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HcEmpty from '~/components/Empty.vue'
|
import HcEmpty from '~/components/Empty.vue'
|
||||||
import HcPostCard from '~/components/PostCard'
|
import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||||
import HcCategory from '~/components/Category'
|
import HcCategory from '~/components/Category'
|
||||||
import HcHashtag from '~/components/Hashtag/Hashtag'
|
import HcHashtag from '~/components/Hashtag/Hashtag'
|
||||||
import { relatedContributions } from '~/graphql/PostQuery'
|
import { relatedContributions } from '~/graphql/PostQuery'
|
||||||
|
|||||||
@ -234,6 +234,8 @@
|
|||||||
:post="post"
|
:post="post"
|
||||||
:width="{ base: '100%', md: '100%', xl: '50%' }"
|
:width="{ base: '100%', md: '100%', xl: '50%' }"
|
||||||
@removePostFromList="removePostFromList"
|
@removePostFromList="removePostFromList"
|
||||||
|
@pinPost="pinPost"
|
||||||
|
@unpinPost="unpinPost"
|
||||||
/>
|
/>
|
||||||
</masonry-grid-item>
|
</masonry-grid-item>
|
||||||
</template>
|
</template>
|
||||||
@ -268,7 +270,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import uniqBy from 'lodash/uniqBy'
|
import uniqBy from 'lodash/uniqBy'
|
||||||
import User from '~/components/User/User'
|
import User from '~/components/User/User'
|
||||||
import HcPostCard from '~/components/PostCard'
|
import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||||
import HcFollowButton from '~/components/FollowButton.vue'
|
import HcFollowButton from '~/components/FollowButton.vue'
|
||||||
import HcCountTo from '~/components/CountTo.vue'
|
import HcCountTo from '~/components/CountTo.vue'
|
||||||
import HcBadges from '~/components/Badges.vue'
|
import HcBadges from '~/components/Badges.vue'
|
||||||
@ -279,9 +281,10 @@ import HcUpload from '~/components/Upload'
|
|||||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
||||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||||
import { filterPosts } from '~/graphql/PostQuery'
|
import { profilePagePosts } from '~/graphql/PostQuery'
|
||||||
import UserQuery from '~/graphql/User'
|
import UserQuery from '~/graphql/User'
|
||||||
import { Block, Unblock } from '~/graphql/settings/BlockedUsers'
|
import { Block, Unblock } from '~/graphql/settings/BlockedUsers'
|
||||||
|
import PostMutations from '~/graphql/PostMutations'
|
||||||
|
|
||||||
const tabToFilterMapping = ({ tab, id }) => {
|
const tabToFilterMapping = ({ tab, id }) => {
|
||||||
return {
|
return {
|
||||||
@ -412,6 +415,32 @@ export default {
|
|||||||
this.resetPostList()
|
this.resetPostList()
|
||||||
this.$apollo.queries.Post.refetch()
|
this.$apollo.queries.Post.refetch()
|
||||||
},
|
},
|
||||||
|
pinPost(post) {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: PostMutations().pinPost,
|
||||||
|
variables: { id: post.id },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('post.menu.pinnedSuccessfully'))
|
||||||
|
this.resetPostList()
|
||||||
|
this.$apollo.queries.Post.refetch()
|
||||||
|
})
|
||||||
|
.catch(error => this.$toast.error(error.message))
|
||||||
|
},
|
||||||
|
unpinPost(post) {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: PostMutations().unpinPost,
|
||||||
|
variables: { id: post.id },
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$t('post.menu.unpinnedSuccessfully'))
|
||||||
|
this.resetPostList()
|
||||||
|
this.$apollo.queries.Post.refetch()
|
||||||
|
})
|
||||||
|
.catch(error => this.$toast.error(error.message))
|
||||||
|
},
|
||||||
optimisticFollow({ followedByCurrentUser }) {
|
optimisticFollow({ followedByCurrentUser }) {
|
||||||
/*
|
/*
|
||||||
* Note: followedByCountStartValue is updated to avoid counting from 0 when follow/unfollow
|
* Note: followedByCountStartValue is updated to avoid counting from 0 when follow/unfollow
|
||||||
@ -437,18 +466,18 @@ export default {
|
|||||||
apollo: {
|
apollo: {
|
||||||
Post: {
|
Post: {
|
||||||
query() {
|
query() {
|
||||||
return filterPosts(this.$i18n)
|
return profilePagePosts(this.$i18n)
|
||||||
},
|
},
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
filter: this.filter,
|
filter: this.filter,
|
||||||
first: this.pageSize,
|
first: this.pageSize,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
orderBy: 'createdAt_desc',
|
orderBy: ['pinnedAt_asc', 'createdAt_desc'],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update({ Post }) {
|
update({ profilePagePosts }) {
|
||||||
this.posts = Post
|
this.posts = profilePagePosts
|
||||||
},
|
},
|
||||||
fetchPolicy: 'cache-and-network',
|
fetchPolicy: 'cache-and-network',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -31,11 +31,7 @@ export default ({ app = {} }) => {
|
|||||||
if (length <= 0) {
|
if (length <= 0) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
let output = trunc(value, length).html
|
return trunc(value, length).html
|
||||||
if (output.length < value.length) {
|
|
||||||
output += ' …'
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
},
|
},
|
||||||
list: (value, glue = ', ', truncate = 0) => {
|
list: (value, glue = ', ', truncate = 0) => {
|
||||||
if (!Array.isArray(value) || !value.length) {
|
if (!Array.isArray(value) || !value.length) {
|
||||||
|
|||||||
134
webapp/yarn.lock
134
webapp/yarn.lock
@ -2672,10 +2672,10 @@
|
|||||||
"@vue/babel-plugin-transform-vue-jsx" "^1.0.0"
|
"@vue/babel-plugin-transform-vue-jsx" "^1.0.0"
|
||||||
camelcase "^5.0.0"
|
camelcase "^5.0.0"
|
||||||
|
|
||||||
"@vue/cli-shared-utils@~3.12.0":
|
"@vue/cli-shared-utils@~4.0.4":
|
||||||
version "3.12.0"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.12.0.tgz#48fcd786129cf02278b9c91f2c3491199f777248"
|
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.0.4.tgz#ef94325c3954eaac60ca4af9a6de355131ae6e3f"
|
||||||
integrity sha512-8XEn4s0Cc+98eqdGSQJSrzSKIsf0FMDmfDvgXjT7I2qZWs9e0toOAm7RooypRSad2FhwxzY2bLPgCkNPDJN/jQ==
|
integrity sha512-f8a9MxZJ89zw783gk2yjG6wPu5IHnmrH8whc1jMJhWNKxRTgCkUxevPVQIobiWy1UtMBdDXXOLxd4PRNK9nyxQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^15.0.1"
|
"@hapi/joi" "^15.0.1"
|
||||||
chalk "^2.4.1"
|
chalk "^2.4.1"
|
||||||
@ -2687,7 +2687,7 @@
|
|||||||
ora "^3.4.0"
|
ora "^3.4.0"
|
||||||
request "^2.87.0"
|
request "^2.87.0"
|
||||||
request-promise-native "^1.0.7"
|
request-promise-native "^1.0.7"
|
||||||
semver "^6.0.0"
|
semver "^6.1.0"
|
||||||
string.prototype.padstart "^3.0.0"
|
string.prototype.padstart "^3.0.0"
|
||||||
|
|
||||||
"@vue/component-compiler-utils@^3.0.0":
|
"@vue/component-compiler-utils@^3.0.0":
|
||||||
@ -7030,22 +7030,7 @@ execa@^1.0.0:
|
|||||||
signal-exit "^3.0.0"
|
signal-exit "^3.0.0"
|
||||||
strip-eof "^1.0.0"
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
execa@^2.0.4:
|
execa@^2.0.4, execa@^2.1.0:
|
||||||
version "2.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-2.0.4.tgz#2f5cc589c81db316628627004ea4e37b93391d8e"
|
|
||||||
integrity sha512-VcQfhuGD51vQUQtKIq2fjGDLDbL6N1DTQVpYzxZ7LPIXw3HqTuIz6uxRmpV1qf8i31LHf2kjiaGI+GdHwRgbnQ==
|
|
||||||
dependencies:
|
|
||||||
cross-spawn "^6.0.5"
|
|
||||||
get-stream "^5.0.0"
|
|
||||||
is-stream "^2.0.0"
|
|
||||||
merge-stream "^2.0.0"
|
|
||||||
npm-run-path "^3.0.0"
|
|
||||||
onetime "^5.1.0"
|
|
||||||
p-finally "^2.0.0"
|
|
||||||
signal-exit "^3.0.2"
|
|
||||||
strip-final-newline "^2.0.0"
|
|
||||||
|
|
||||||
execa@^2.1.0:
|
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-2.1.0.tgz#e5d3ecd837d2a60ec50f3da78fd39767747bbe99"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-2.1.0.tgz#e5d3ecd837d2a60ec50f3da78fd39767747bbe99"
|
||||||
integrity sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==
|
integrity sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==
|
||||||
@ -12901,10 +12886,10 @@ prosemirror-commands@^1.0.8:
|
|||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
prosemirror-transform "^1.0.0"
|
prosemirror-transform "^1.0.0"
|
||||||
|
|
||||||
prosemirror-dropcursor@^1.1.2:
|
prosemirror-dropcursor@^1.2.0:
|
||||||
version "1.1.2"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.2.tgz#d54428e0fdbc0fb3d4c5809acd1ad031e6cb6855"
|
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.2.0.tgz#00b1bdb803ac28f5a68d7e0a16a47c9c11aab97d"
|
||||||
integrity sha512-QHZbYPr8AY0g88TC/Wp7jpYbUoSpTSO8sqHNGvvZOInsAyylIdOpsrfhY1NC+/lh+iuwka0YogGtq2mmE7cr4g==
|
integrity sha512-D7JrvOgN32PmOgfimdDMKCuYp4tGyCulpsd39/Nzvn9A+tCJmM8XY1PB07zkr2vjrjF09WYD3Ifer7Z3pk/YRw==
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
prosemirror-transform "^1.1.0"
|
prosemirror-transform "^1.1.0"
|
||||||
@ -12937,7 +12922,7 @@ prosemirror-inputrules@^1.0.4:
|
|||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
prosemirror-transform "^1.0.0"
|
prosemirror-transform "^1.0.0"
|
||||||
|
|
||||||
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.1:
|
prosemirror-keymap@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz#03ef32b828e3a859dfb570eb84928bf2e5330bc2"
|
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz#03ef32b828e3a859dfb570eb84928bf2e5330bc2"
|
||||||
integrity sha512-e79ApE7PXXZMFtPz7WbjycjAFd1NPjgY1MkecVz98tqwlBSggXWXYQnWFk6x7UkmnBYRHHbXHkR/RXmu2wyBJg==
|
integrity sha512-e79ApE7PXXZMFtPz7WbjycjAFd1NPjgY1MkecVz98tqwlBSggXWXYQnWFk6x7UkmnBYRHHbXHkR/RXmu2wyBJg==
|
||||||
@ -12945,17 +12930,25 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.1:
|
|||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
w3c-keyname "^1.1.8"
|
w3c-keyname "^1.1.8"
|
||||||
|
|
||||||
prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.3:
|
prosemirror-keymap@^1.0.2:
|
||||||
version "1.7.3"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.7.3.tgz#d843b9338ebb1c22db85681452cf5724f785906e"
|
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.2.tgz#e4e8b876a586ec6a170615fce19c4450f4075bef"
|
||||||
integrity sha512-Es71i2qXdkJNyIFyH7QoKDnKCTVC4LaQgiAaQV5Zd5XCKHg09m9NIJCEgePrF2yN/1tB/C5NYDY/4QsPvEM59A==
|
integrity sha512-aq3fBT3WMbwGNacUtMbS/mxd87hjJyjtUx5/h3q/P3FiVqHxmeA9snxQsZHYe0cWRziZePun8zw6kHFKLp/DAQ==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-state "^1.0.0"
|
||||||
|
w3c-keyname "^2.0.0"
|
||||||
|
|
||||||
|
prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.4:
|
||||||
|
version "1.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.7.4.tgz#fadefad98ef9d71ca1749d18a82005313c978e4b"
|
||||||
|
integrity sha512-yxdpPh9Uv5vAOZvmbhg4fsGUK1oHuQs69iX7cFZ0A4Y+AyMMWRCNKUt21uv84HbXb4I180l4pJE8ibaH/SwYiw==
|
||||||
dependencies:
|
dependencies:
|
||||||
orderedmap "^1.0.0"
|
orderedmap "^1.0.0"
|
||||||
|
|
||||||
prosemirror-schema-list@^1.0.3:
|
prosemirror-schema-list@^1.0.4:
|
||||||
version "1.0.3"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.0.3.tgz#539caafa4f9314000943bd783be4017165ec0bd6"
|
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.0.4.tgz#cbb11936e306aa45586af4279529ce61b52cfb6e"
|
||||||
integrity sha512-+zzSawVds8LsZpl/bLTCYk2lYactF93W219Czh81zBILikCRDOHjp1CQ1os4ZXBp6LlD+JnBqF1h59Q+hilOoQ==
|
integrity sha512-7Y0b6FIG6ATnCcDSLrZfU9yIfOG5Yad3DMNZ9W7GGfMSzdIl0aHExrsIUgviJZjovO2jtLJVbxWGjMR3OrTupA==
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-model "^1.0.0"
|
prosemirror-model "^1.0.0"
|
||||||
prosemirror-transform "^1.0.0"
|
prosemirror-transform "^1.0.0"
|
||||||
@ -12991,10 +12984,10 @@ prosemirror-utils@^0.9.6:
|
|||||||
resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.9.6.tgz#3d97bd85897e3b535555867dc95a51399116a973"
|
resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.9.6.tgz#3d97bd85897e3b535555867dc95a51399116a973"
|
||||||
integrity sha512-UC+j9hQQ1POYfMc5p7UFxBTptRiGPR7Kkmbl3jVvU8VgQbkI89tR/GK+3QYC8n+VvBZrtAoCrJItNhWSxX3slA==
|
integrity sha512-UC+j9hQQ1POYfMc5p7UFxBTptRiGPR7Kkmbl3jVvU8VgQbkI89tR/GK+3QYC8n+VvBZrtAoCrJItNhWSxX3slA==
|
||||||
|
|
||||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.11.4:
|
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.11.7:
|
||||||
version "1.11.4"
|
version "1.11.7"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.11.4.tgz#f80aec8924d59d4c3456dcc5bfea733758ec9b40"
|
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.11.7.tgz#537020acab43e18d51e18717fb1f39466317ce21"
|
||||||
integrity sha512-J0g7xiCDx+p3CtpC69E7HvMmnW7yCILEhOXxSANZPX8iIwUrVTfdWKAzufi9F9MoM08ewsaF254xV90NpkGWVQ==
|
integrity sha512-RDll1mhrOQ5JPOsUZlISH3VP/4zyHm3RAuqh9Cg5HdpfSQow0nsx8xL5YQZ976UdhvwbkiKamLtxhSRKES9wsA==
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-model "^1.1.0"
|
prosemirror-model "^1.1.0"
|
||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
@ -15410,62 +15403,62 @@ tippy.js@^4.3.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
popper.js "^1.14.7"
|
popper.js "^1.14.7"
|
||||||
|
|
||||||
tiptap-commands@^1.12.2:
|
tiptap-commands@^1.12.3:
|
||||||
version "1.12.2"
|
version "1.12.3"
|
||||||
resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.12.2.tgz#5d478604f03ab5bc5b05e3f94f8c75cec175c8c3"
|
resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.12.3.tgz#604767878073e6344d1daf7a376fd89fc62e4742"
|
||||||
integrity sha512-wE19avtU0N/pNQlwDhfsJH1M3QT0Bc3oukfoB7K+K0H7xAXyfukbVPoZGjItWoyawtPk11A80OIBQCrO63fO4Q==
|
integrity sha512-Dck51lePBwuHmkvkJ6+8V3DbInxAhZwtS2mPvVwz74pDUIcy17tCFw1eHUN50JoXIAci7acuxPKO/weVO1JAyw==
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-commands "^1.0.8"
|
prosemirror-commands "^1.0.8"
|
||||||
prosemirror-inputrules "^1.0.4"
|
prosemirror-inputrules "^1.0.4"
|
||||||
prosemirror-model "^1.7.3"
|
prosemirror-model "^1.7.4"
|
||||||
prosemirror-schema-list "^1.0.3"
|
prosemirror-schema-list "^1.0.4"
|
||||||
prosemirror-state "^1.2.4"
|
prosemirror-state "^1.2.4"
|
||||||
prosemirror-tables "^0.9.5"
|
prosemirror-tables "^0.9.5"
|
||||||
prosemirror-utils "^0.9.6"
|
prosemirror-utils "^0.9.6"
|
||||||
tiptap-utils "^1.8.1"
|
tiptap-utils "^1.8.2"
|
||||||
|
|
||||||
tiptap-extensions@~1.28.3:
|
tiptap-extensions@~1.28.4:
|
||||||
version "1.28.3"
|
version "1.28.4"
|
||||||
resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.28.3.tgz#6278ed247c35c97cec41395671433408977cd9ef"
|
resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.28.4.tgz#0e729d081a80105730101512e7eb5acdce8b9bde"
|
||||||
integrity sha512-iTUY5HQ+gYCHlyGMqcfKqO7JAvCqDEuvO4vEUrVxx0RgoYQfEf+Y17Kw8AJlVR6bB/Y/bEmMIuzVPjsgWNWdbw==
|
integrity sha512-UAtxngKifjrMtJFmi3D9RCNC5LJutq4yn1Np0cqJ4dTnvhWR49PqN6gKjlMYyzyutiLLQk+/3GM/E6EfVwmHOA==
|
||||||
dependencies:
|
dependencies:
|
||||||
lowlight "^1.12.1"
|
lowlight "^1.12.1"
|
||||||
prosemirror-collab "^1.1.2"
|
prosemirror-collab "^1.1.2"
|
||||||
prosemirror-history "^1.0.4"
|
prosemirror-history "^1.0.4"
|
||||||
prosemirror-model "^1.7.3"
|
prosemirror-model "^1.7.4"
|
||||||
prosemirror-state "^1.2.4"
|
prosemirror-state "^1.2.4"
|
||||||
prosemirror-tables "^0.9.5"
|
prosemirror-tables "^0.9.5"
|
||||||
prosemirror-transform "^1.1.5"
|
prosemirror-transform "^1.1.5"
|
||||||
prosemirror-utils "^0.9.6"
|
prosemirror-utils "^0.9.6"
|
||||||
prosemirror-view "^1.11.4"
|
prosemirror-view "^1.11.7"
|
||||||
tiptap "^1.26.3"
|
tiptap "^1.26.4"
|
||||||
tiptap-commands "^1.12.2"
|
tiptap-commands "^1.12.3"
|
||||||
|
|
||||||
tiptap-utils@^1.8.1:
|
tiptap-utils@^1.8.2:
|
||||||
version "1.8.1"
|
version "1.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.8.1.tgz#52eb90524f1ec95e66ddc84a20d892aaac879630"
|
resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.8.2.tgz#f07a2053c6ac9fbbb4f02e0844b326d0e6c8b7fb"
|
||||||
integrity sha512-FcceXo+yVZni54aB/R3nTpdtcHmFM6QwW6PZg1aHH2u2fhkeV/MB7sXBkx3wIrvOtw8WPT2Kjpou2to27CCtbA==
|
integrity sha512-pyx+3p4fICGM7JU1mcsnRx5jXvLrCL8Nm/9yjeWEZXpAC85L/btY0eFo2Oz4+dKg39+1EGNHheodujx3ngw4lQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-model "^1.7.3"
|
prosemirror-model "^1.7.4"
|
||||||
prosemirror-state "^1.2.4"
|
prosemirror-state "^1.2.4"
|
||||||
prosemirror-tables "^0.9.5"
|
prosemirror-tables "^0.9.5"
|
||||||
prosemirror-utils "^0.9.6"
|
prosemirror-utils "^0.9.6"
|
||||||
|
|
||||||
tiptap@^1.26.3, tiptap@~1.26.3:
|
tiptap@^1.26.4, tiptap@~1.26.3:
|
||||||
version "1.26.3"
|
version "1.26.4"
|
||||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.26.3.tgz#a08e1db4f1dce17a14309532e65a3949b0ceed91"
|
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.26.4.tgz#bfa289841bc45c6401cbd1661a02b81c3d3f14f0"
|
||||||
integrity sha512-EcTEM8GLuMa1jNxGg5cWR7NqyiFwtRat6im8A5EvL6iiLiOhIaqgkQnZJ5qUxWNgQTfjgCO5IWA85yoRSJWNMQ==
|
integrity sha512-UCH0wufjGdKMuCUydL896sFYXEUWC3bE20h/oONABSf0gull+pqBEm7J1yCl7j50eYa9FiLgUBGPqPTzKLluxQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-commands "^1.0.8"
|
prosemirror-commands "^1.0.8"
|
||||||
prosemirror-dropcursor "^1.1.2"
|
prosemirror-dropcursor "^1.2.0"
|
||||||
prosemirror-gapcursor "^1.0.4"
|
prosemirror-gapcursor "^1.0.4"
|
||||||
prosemirror-inputrules "^1.0.4"
|
prosemirror-inputrules "^1.0.4"
|
||||||
prosemirror-keymap "^1.0.1"
|
prosemirror-keymap "^1.0.2"
|
||||||
prosemirror-model "^1.7.3"
|
prosemirror-model "^1.7.4"
|
||||||
prosemirror-state "^1.2.4"
|
prosemirror-state "^1.2.4"
|
||||||
prosemirror-view "^1.11.4"
|
prosemirror-view "^1.11.7"
|
||||||
tiptap-commands "^1.12.2"
|
tiptap-commands "^1.12.3"
|
||||||
tiptap-utils "^1.8.1"
|
tiptap-utils "^1.8.2"
|
||||||
|
|
||||||
title-case@^2.1.0:
|
title-case@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
@ -16347,6 +16340,11 @@ w3c-keyname@^1.1.8:
|
|||||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c"
|
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c"
|
||||||
integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==
|
integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==
|
||||||
|
|
||||||
|
w3c-keyname@^2.0.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.1.1.tgz#d234195b0a79913d06a0bc636f17f00050a5de56"
|
||||||
|
integrity sha512-8kSrsGClLiL4kb5/pTxglejUlEAPk3GXtkBblSMrQDxKz0NkMRTVTPBZm6QCNqPOCPsdNvae5XfV+RJZgeGXEA==
|
||||||
|
|
||||||
walker@^1.0.7, walker@~1.0.5:
|
walker@^1.0.7, walker@~1.0.5:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
|
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
|
||||||
|
|||||||
11
yarn.lock
11
yarn.lock
@ -1895,10 +1895,10 @@ cypress-cucumber-preprocessor@^1.16.2:
|
|||||||
js-string-escape "^1.0.1"
|
js-string-escape "^1.0.1"
|
||||||
through "^2.3.8"
|
through "^2.3.8"
|
||||||
|
|
||||||
cypress-file-upload@^3.3.4:
|
cypress-file-upload@^3.4.0:
|
||||||
version "3.3.4"
|
version "3.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.3.4.tgz#cbeb8a7a07150a1c60f2873666979e48b6335070"
|
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.4.0.tgz#f066853357994ed7b64e0ea35920d3d85273914e"
|
||||||
integrity sha512-kfdrQ6cWBw82G7EbHSqZJiOQWRh9cGz9K1mjePNZax00gBL0qOdRTjfkAnR2vEmmJyCfnN3efryjfhFeLrGWVw==
|
integrity sha512-BY7jrpOPFEGcGBzkTReEjwQ59+O3u2SH2OleXdnDCuWIPHjbDx7haXukyAFd906JsI4Z2zXPiKrUVFHZc96eFA==
|
||||||
|
|
||||||
cypress-plugin-retries@^1.3.0:
|
cypress-plugin-retries@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
@ -2359,7 +2359,8 @@ extsprintf@^1.2.0:
|
|||||||
|
|
||||||
faker@Marak/faker.js#master:
|
faker@Marak/faker.js#master:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://codeload.github.com/Marak/faker.js/tar.gz/10bfb9f467b0ac2b8912ffc15690b50ef3244f09"
|
uid "9fd8d7d37b398842d0784a116a340f7aa6afb89b"
|
||||||
|
resolved "https://codeload.github.com/Marak/faker.js/tar.gz/9fd8d7d37b398842d0784a116a340f7aa6afb89b"
|
||||||
|
|
||||||
fast-deep-equal@^2.0.1:
|
fast-deep-equal@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user