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 779-tags-of-contribution-in-text
This commit is contained in:
commit
b187fbb7b2
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@ cypress.env.json
|
||||
|
||||
!.gitkeep
|
||||
**/coverage
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:12.4-alpine as base
|
||||
FROM node:12.5-alpine as base
|
||||
LABEL Description="Backend of the Social Network Human-Connection.org" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.2",
|
||||
"graphql-shield": "~5.6.1",
|
||||
"graphql-shield": "~6.0.2",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.18.0",
|
||||
"helmet": "~3.18.0",
|
||||
|
||||
@ -87,6 +87,9 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||
}
|
||||
|
||||
const session = driver.session()
|
||||
if (data.place_type.length > 1) {
|
||||
data.id = 'region.' + data.id.split('.')[1]
|
||||
}
|
||||
await createLocation(session, data)
|
||||
|
||||
let parent = data
|
||||
|
||||
@ -4,7 +4,9 @@ import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||
* TODO: implement
|
||||
* See: https://github.com/Human-Connection/Nitro-Backend/pull/40#pullrequestreview-180898363
|
||||
*/
|
||||
const isAuthenticated = rule()(async (parent, args, ctx, info) => {
|
||||
const isAuthenticated = rule({
|
||||
cache: 'contextual',
|
||||
})(async (_parent, _args, ctx, _info) => {
|
||||
return ctx.user !== null
|
||||
})
|
||||
|
||||
|
||||
@ -17,6 +17,10 @@ export default {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdatePost: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreateUser: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'User')))
|
||||
return resolve(root, args, context, info)
|
||||
|
||||
@ -18,9 +18,6 @@ export default {
|
||||
if (!params.content || content.length < COMMENT_MIN_LENGTH) {
|
||||
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
|
||||
}
|
||||
if (!postId.trim()) {
|
||||
throw new UserInputError(NO_POST_ERR_MESSAGE)
|
||||
}
|
||||
|
||||
const session = context.driver.session()
|
||||
const postQueryRes = await session.run(
|
||||
|
||||
@ -23,7 +23,7 @@ afterEach(async () => {
|
||||
|
||||
describe('CreateComment', () => {
|
||||
const createCommentMutation = gql`
|
||||
mutation($postId: ID, $content: String!) {
|
||||
mutation($postId: ID!, $content: String!) {
|
||||
CreateComment(postId: $postId, content: $content) {
|
||||
id
|
||||
content
|
||||
@ -37,13 +37,6 @@ describe('CreateComment', () => {
|
||||
}
|
||||
}
|
||||
`
|
||||
const commentQueryForPostId = gql`
|
||||
query($content: String) {
|
||||
Comment(content: $content) {
|
||||
postId
|
||||
}
|
||||
}
|
||||
`
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
createCommentVariables = {
|
||||
@ -191,23 +184,6 @@ describe('CreateComment', () => {
|
||||
client.request(createCommentMutation, createCommentVariablesWithNonExistentPost),
|
||||
).rejects.toThrow('Comment cannot be created without a post!')
|
||||
})
|
||||
|
||||
it('does not create the comment with the postId as an attribute', async () => {
|
||||
const commentQueryVariablesByContent = {
|
||||
content: "I'm authorised to comment",
|
||||
}
|
||||
|
||||
await client.request(createCommentMutation, createCommentVariables)
|
||||
const { Comment } = await client.request(
|
||||
commentQueryForPostId,
|
||||
commentQueryVariablesByContent,
|
||||
)
|
||||
expect(Comment).toEqual([
|
||||
{
|
||||
postId: null,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,30 +1,74 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import uuid from 'uuid/v4'
|
||||
import fileUpload from './fileUpload'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
UpdatePost: async (object, params, context, resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
delete params.categoryIds
|
||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
const session = context.driver.session()
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
RETURN post, category
|
||||
`
|
||||
|
||||
await session.run(cypherDeletePreviousRelations, { params })
|
||||
|
||||
let updatePostCypher = `MATCH (post:Post {id: $params.id})
|
||||
SET post = $params
|
||||
`
|
||||
if (categoryIds && categoryIds.length) {
|
||||
updatePostCypher += `WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (post)-[:CATEGORIZED]->(category)
|
||||
`
|
||||
}
|
||||
updatePostCypher += `RETURN post`
|
||||
const updatePostVariables = { categoryIds, params }
|
||||
|
||||
const transactionRes = await session.run(updatePostCypher, updatePostVariables)
|
||||
const [post] = transactionRes.records.map(record => {
|
||||
return record.get('post')
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return post.properties
|
||||
},
|
||||
|
||||
CreatePost: async (object, params, context, resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
delete params.categoryIds
|
||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||
const result = await neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
params.id = params.id || uuid()
|
||||
let createPostCypher = `CREATE (post:Post {params})
|
||||
WITH post
|
||||
MATCH (author:User {id: $userId})
|
||||
MERGE (post)<-[:WROTE]-(author)
|
||||
`
|
||||
if (categoryIds) {
|
||||
createPostCypher += `WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (post)-[:CATEGORIZED]->(category)
|
||||
`
|
||||
}
|
||||
createPostCypher += `RETURN post`
|
||||
const createPostVariables = { userId: context.user.id, categoryIds, params }
|
||||
|
||||
const session = context.driver.session()
|
||||
await session.run(
|
||||
'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' +
|
||||
'MERGE (post)<-[:WROTE]-(author) ' +
|
||||
'RETURN author',
|
||||
{
|
||||
userId: context.user.id,
|
||||
postId: result.id,
|
||||
},
|
||||
)
|
||||
const transactionRes = await session.run(createPostCypher, createPostVariables)
|
||||
|
||||
const [post] = transactionRes.records.map(record => {
|
||||
return record.get('post')
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return result
|
||||
return post.properties
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,7 +4,34 @@ import { host, login } from '../../jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
|
||||
const postTitle = 'I am a title'
|
||||
const postContent = 'Some content'
|
||||
const oldTitle = 'Old title'
|
||||
const oldContent = 'Old content'
|
||||
const newTitle = 'New title'
|
||||
const newContent = 'New content'
|
||||
const createPostVariables = { title: postTitle, content: postContent }
|
||||
const createPostWithCategoriesMutation = `
|
||||
mutation($title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
const creatPostWithCategoriesVariables = {
|
||||
title: postTitle,
|
||||
content: postContent,
|
||||
categoryIds: ['cat9', 'cat4', 'cat15'],
|
||||
}
|
||||
const postQueryWithCategories = `
|
||||
query($id: ID) {
|
||||
Post(id: $id) {
|
||||
categories {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
email: 'test@example.org',
|
||||
@ -18,8 +45,8 @@ afterEach(async () => {
|
||||
|
||||
describe('CreatePost', () => {
|
||||
const mutation = `
|
||||
mutation {
|
||||
CreatePost(title: "I am a title", content: "Some content") {
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
title
|
||||
content
|
||||
slug
|
||||
@ -32,7 +59,7 @@ describe('CreatePost', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation)).rejects.toThrow('Not Authorised')
|
||||
await expect(client.request(mutation, createPostVariables)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
@ -46,15 +73,15 @@ describe('CreatePost', () => {
|
||||
it('creates a post', async () => {
|
||||
const expected = {
|
||||
CreatePost: {
|
||||
title: 'I am a title',
|
||||
content: 'Some content',
|
||||
title: postTitle,
|
||||
content: postContent,
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation)).resolves.toMatchObject(expected)
|
||||
await expect(client.request(mutation, createPostVariables)).resolves.toMatchObject(expected)
|
||||
})
|
||||
|
||||
it('assigns the authenticated user as author', async () => {
|
||||
await client.request(mutation)
|
||||
await client.request(mutation, createPostVariables)
|
||||
const { User } = await client.request(
|
||||
`{
|
||||
User(email:"test@example.org") {
|
||||
@ -65,49 +92,75 @@ describe('CreatePost', () => {
|
||||
}`,
|
||||
{ headers },
|
||||
)
|
||||
expect(User).toEqual([{ contributions: [{ title: 'I am a title' }] }])
|
||||
expect(User).toEqual([{ contributions: [{ title: postTitle }] }])
|
||||
})
|
||||
|
||||
describe('disabled and deleted', () => {
|
||||
it('initially false', async () => {
|
||||
const expected = { CreatePost: { disabled: false, deleted: false } }
|
||||
await expect(client.request(mutation)).resolves.toMatchObject(expected)
|
||||
await expect(client.request(mutation, createPostVariables)).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('language', () => {
|
||||
it('allows a user to set the language of the post', async () => {
|
||||
const createPostWithLanguageMutation = `
|
||||
mutation {
|
||||
CreatePost(title: "I am a title", content: "Some content", language: "en") {
|
||||
mutation($title: String!, $content: String!, $language: String) {
|
||||
CreatePost(title: $title, content: $content, language: $language) {
|
||||
language
|
||||
}
|
||||
}
|
||||
`
|
||||
const createPostWithLanguageVariables = {
|
||||
title: postTitle,
|
||||
content: postContent,
|
||||
language: 'en',
|
||||
}
|
||||
const expected = { CreatePost: { language: 'en' } }
|
||||
await expect(client.request(createPostWithLanguageMutation)).resolves.toEqual(
|
||||
expect.objectContaining(expected),
|
||||
await expect(
|
||||
client.request(createPostWithLanguageMutation, createPostWithLanguageVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expected))
|
||||
})
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
it('allows a user to set the categories of the post', async () => {
|
||||
await Promise.all([
|
||||
factory.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat4',
|
||||
name: 'Environment & Nature',
|
||||
icon: 'tree',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat15',
|
||||
name: 'Consumption & Sustainability',
|
||||
icon: 'shopping-cart',
|
||||
}),
|
||||
])
|
||||
const expected = [{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]
|
||||
const postWithCategories = await client.request(
|
||||
createPostWithCategoriesMutation,
|
||||
creatPostWithCategoriesVariables,
|
||||
)
|
||||
const postQueryWithCategoriesVariables = {
|
||||
id: postWithCategories.CreatePost.id,
|
||||
}
|
||||
await expect(
|
||||
client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
|
||||
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdatePost', () => {
|
||||
const mutation = `
|
||||
mutation($id: ID!, $content: String) {
|
||||
UpdatePost(id: $id, content: $content) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
let variables = {
|
||||
id: 'p1',
|
||||
content: 'New content',
|
||||
}
|
||||
|
||||
let updatePostMutation
|
||||
let updatePostVariables
|
||||
beforeEach(async () => {
|
||||
const asAuthor = Factory()
|
||||
await asAuthor.create('User', {
|
||||
@ -120,14 +173,32 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
await asAuthor.create('Post', {
|
||||
id: 'p1',
|
||||
content: 'Old content',
|
||||
title: oldTitle,
|
||||
content: oldContent,
|
||||
})
|
||||
updatePostMutation = `
|
||||
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
updatePostVariables = {
|
||||
id: 'p1',
|
||||
title: newTitle,
|
||||
content: newContent,
|
||||
categoryIds: null,
|
||||
}
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -139,7 +210,9 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -151,8 +224,59 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
it('updates a post', async () => {
|
||||
const expected = { UpdatePost: { id: 'p1', content: 'New content' } }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
const expected = { UpdatePost: { id: 'p1', content: newContent } }
|
||||
await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual(
|
||||
expected,
|
||||
)
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
let postWithCategories
|
||||
beforeEach(async () => {
|
||||
await Promise.all([
|
||||
factory.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat4',
|
||||
name: 'Environment & Nature',
|
||||
icon: 'tree',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat15',
|
||||
name: 'Consumption & Sustainability',
|
||||
icon: 'shopping-cart',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat27',
|
||||
name: 'Animal Protection',
|
||||
icon: 'paw',
|
||||
}),
|
||||
])
|
||||
postWithCategories = await client.request(
|
||||
createPostWithCategoriesMutation,
|
||||
creatPostWithCategoriesVariables,
|
||||
)
|
||||
updatePostVariables = {
|
||||
id: postWithCategories.CreatePost.id,
|
||||
title: newTitle,
|
||||
content: newContent,
|
||||
categoryIds: ['cat27'],
|
||||
}
|
||||
})
|
||||
|
||||
it('allows a user to update the categories of a post', async () => {
|
||||
await client.request(updatePostMutation, updatePostVariables)
|
||||
const expected = [{ id: 'cat27' }]
|
||||
const postQueryWithCategoriesVariables = {
|
||||
id: postWithCategories.CreatePost.id,
|
||||
}
|
||||
await expect(
|
||||
client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
|
||||
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,12 +11,31 @@ export default {
|
||||
description: description,
|
||||
}
|
||||
|
||||
const reportQueryRes = await session.run(
|
||||
`
|
||||
match (u:User {id:$submitterId}) -[:REPORTED]->(report)-[:REPORTED]-> (resource {id: $resourceId})
|
||||
return labels(resource)[0] as label
|
||||
`,
|
||||
{
|
||||
resourceId: id,
|
||||
submitterId: user.id,
|
||||
},
|
||||
)
|
||||
const [rep] = reportQueryRes.records.map(record => {
|
||||
return {
|
||||
label: record.get('label'),
|
||||
}
|
||||
})
|
||||
|
||||
if (rep) {
|
||||
throw new Error(rep.label)
|
||||
}
|
||||
const res = await session.run(
|
||||
`
|
||||
MATCH (submitter:User {id: $userId})
|
||||
MATCH (resource {id: $resourceId})
|
||||
WHERE resource:User OR resource:Comment OR resource:Post
|
||||
CREATE (report:Report $reportData)
|
||||
MERGE (report:Report {id: {reportData}.id })
|
||||
MERGE (resource)<-[:REPORTED]-(report)
|
||||
MERGE (report)<-[:REPORTED]-(submitter)
|
||||
RETURN report, submitter, resource, labels(resource)[0] as type
|
||||
@ -27,6 +46,7 @@ export default {
|
||||
reportData,
|
||||
},
|
||||
)
|
||||
|
||||
session.close()
|
||||
|
||||
const [dbResponse] = res.records.map(r => {
|
||||
@ -59,6 +79,7 @@ export default {
|
||||
response.user = resource.properties
|
||||
break
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
},
|
||||
|
||||
@ -13,7 +13,9 @@ describe('report', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
returnedObject = '{ description }'
|
||||
variables = { id: 'whatever' }
|
||||
variables = {
|
||||
id: 'whatever',
|
||||
}
|
||||
headers = {}
|
||||
await factory.create('User', {
|
||||
id: 'u1',
|
||||
@ -42,7 +44,9 @@ describe('report', () => {
|
||||
) ${returnedObject}
|
||||
}
|
||||
`
|
||||
client = new GraphQLClient(host, { headers })
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
})
|
||||
return client.request(mutation, variables)
|
||||
}
|
||||
|
||||
@ -53,7 +57,10 @@ describe('report', () => {
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid resource id', () => {
|
||||
@ -66,19 +73,25 @@ describe('report', () => {
|
||||
|
||||
describe('valid resource id', () => {
|
||||
beforeEach(async () => {
|
||||
variables = { id: 'u2' }
|
||||
variables = {
|
||||
id: 'u2',
|
||||
}
|
||||
})
|
||||
|
||||
it('creates a report', async () => {
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { description: 'Violates code of conduct' },
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
it('creates a report', async () => {
|
||||
await expect(action()).resolves.toEqual({
|
||||
type: null,
|
||||
})
|
||||
})
|
||||
*/
|
||||
it('returns the submitter', async () => {
|
||||
returnedObject = '{ submitter { email } }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { submitter: { email: 'test@example.org' } },
|
||||
report: {
|
||||
submitter: {
|
||||
email: 'test@example.org',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@ -86,50 +99,72 @@ describe('report', () => {
|
||||
it('returns type "User"', async () => {
|
||||
returnedObject = '{ type }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { type: 'User' },
|
||||
report: {
|
||||
type: 'User',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('returns resource in user attribute', async () => {
|
||||
returnedObject = '{ user { name } }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { user: { name: 'abusive-user' } },
|
||||
report: {
|
||||
user: {
|
||||
name: 'abusive-user',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('reported resource is a post', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.authenticateAs({ email: 'test@example.org', password: '1234' })
|
||||
await factory.authenticateAs({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
await factory.create('Post', {
|
||||
id: 'p23',
|
||||
title: 'Matt and Robert having a pair-programming',
|
||||
})
|
||||
variables = { id: 'p23' }
|
||||
variables = {
|
||||
id: 'p23',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns type "Post"', async () => {
|
||||
returnedObject = '{ type }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { type: 'Post' },
|
||||
report: {
|
||||
type: 'Post',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('returns resource in post attribute', async () => {
|
||||
returnedObject = '{ post { title } }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { post: { title: 'Matt and Robert having a pair-programming' } },
|
||||
report: {
|
||||
post: {
|
||||
title: 'Matt and Robert having a pair-programming',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null in user attribute', async () => {
|
||||
returnedObject = '{ user { name } }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { user: null },
|
||||
report: {
|
||||
user: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* 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', () => {
|
||||
beforeEach(async () => {
|
||||
createPostVariables = {
|
||||
@ -147,34 +182,54 @@ describe('report', () => {
|
||||
id: 'c34',
|
||||
content: 'Robert getting tired.',
|
||||
})
|
||||
variables = { id: 'c34' }
|
||||
variables = {
|
||||
id: 'c34',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns type "Comment"', async () => {
|
||||
returnedObject = '{ type }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { type: 'Comment' },
|
||||
report: {
|
||||
type: 'Comment',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('returns resource in comment attribute', async () => {
|
||||
returnedObject = '{ comment { content } }'
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: { comment: { content: 'Robert getting tired.' } },
|
||||
report: {
|
||||
comment: {
|
||||
content: 'Robert getting tired.',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* 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', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('Tag', { id: 't23' })
|
||||
variables = { id: 't23' }
|
||||
await factory.create('Tag', {
|
||||
id: 't23',
|
||||
})
|
||||
variables = {
|
||||
id: 't23',
|
||||
}
|
||||
})
|
||||
|
||||
it('returns null', async () => {
|
||||
await expect(action()).resolves.toEqual({ report: null })
|
||||
await expect(action()).resolves.toEqual({
|
||||
report: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* 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. */
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
type Comment {
|
||||
id: ID!
|
||||
activityId: String
|
||||
postId: ID
|
||||
author: User @relation(name: "WROTE", direction: "IN")
|
||||
content: String!
|
||||
contentExcerpt: String
|
||||
@ -11,4 +10,24 @@ type Comment {
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
}
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
CreateComment(
|
||||
id: ID
|
||||
postId: ID!
|
||||
content: String!
|
||||
contentExcerpt: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
createdAt: String
|
||||
): Comment
|
||||
UpdateComment(
|
||||
id: ID!
|
||||
content: String
|
||||
contentExcerpt: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
): Comment
|
||||
DeleteComment(id: ID!): Comment
|
||||
}
|
||||
|
||||
@ -49,3 +49,42 @@ type Post {
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
CreatePost(
|
||||
id: ID
|
||||
activityId: String
|
||||
objectId: String
|
||||
title: String!
|
||||
slug: String
|
||||
content: String!
|
||||
image: String
|
||||
imageUpload: Upload
|
||||
visibility: Visibility
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
contentExcerpt: String
|
||||
): Post
|
||||
UpdatePost(
|
||||
id: ID!
|
||||
activityId: String
|
||||
objectId: String
|
||||
title: String!
|
||||
slug: String
|
||||
content: String!
|
||||
contentExcerpt: String
|
||||
image: String
|
||||
imageUpload: Upload
|
||||
visibility: Visibility
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
): Post
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export default function(params) {
|
||||
|
||||
return {
|
||||
mutation: `
|
||||
mutation($id: ID!, $postId: ID, $content: String!) {
|
||||
mutation($id: ID!, $postId: ID!, $content: String!) {
|
||||
CreateComment(id: $id, postId: $postId, content: $content) {
|
||||
id
|
||||
}
|
||||
|
||||
@ -8,10 +8,12 @@ export default function create(params) {
|
||||
mutation($id: ID!, $description: String!) {
|
||||
report(description: $description, id: $id) {
|
||||
id
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id, description },
|
||||
variables: {
|
||||
id,
|
||||
description,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1110,10 +1110,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
|
||||
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
|
||||
|
||||
"@types/yup@0.26.16":
|
||||
version "0.26.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.16.tgz#75c428236207c48d9f8062dd1495cda8c5485a15"
|
||||
integrity sha512-E2RNc7DSeQ+2EIJ1H3+yFjYu6YiyQBUJ7yNpIxomrYJ3oFizLZ5yDS3T1JTUNBC2OCRkgnhLS0smob5UuCHfNA==
|
||||
"@types/yup@0.26.20":
|
||||
version "0.26.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.20.tgz#3b85a05f5dd76e2e8475abb6a8aeae7777627143"
|
||||
integrity sha512-LpCsA6NG7vIU7Umv1k4w3YGIBH5ZLZRPEKo8vJLHVbBUqRy2WaJ002kbsRqcwODpkICAOMuyGOqLQJa5isZ8+g==
|
||||
|
||||
"@types/zen-observable@^0.5.3":
|
||||
version "0.5.4"
|
||||
@ -3788,12 +3788,12 @@ graphql-request@~1.8.2:
|
||||
dependencies:
|
||||
cross-fetch "2.2.2"
|
||||
|
||||
graphql-shield@~5.6.1:
|
||||
version "5.6.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.6.2.tgz#27eaad2ce2591ed81b1203e8915df99b28fb5ad5"
|
||||
integrity sha512-DlS6r39s7AaP07yMM6i7GI87UkfL65O1tUPW4kNqp67fD1BU71Ekl7Kt/1L3rxS/gcQdGufuKka5oKUa5GKo2A==
|
||||
graphql-shield@~6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-6.0.2.tgz#3ebad8faacbada91b8e576029732e91b5a041c7f"
|
||||
integrity sha512-3qV2qjeNZla1Fyg6Q2NR5J9AsMaNePLbUboOwhRXB7IcMnTnrxSiVn2R//8VnjnmBjF9rcvgAIAvETZ8AKGfsg==
|
||||
dependencies:
|
||||
"@types/yup" "0.26.16"
|
||||
"@types/yup" "0.26.20"
|
||||
lightercollective "^0.3.0"
|
||||
object-hash "^1.3.1"
|
||||
yup "^0.27.0"
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"cross-env": "^5.2.0",
|
||||
"cypress": "^3.3.2",
|
||||
"cypress-cucumber-preprocessor": "^1.12.0",
|
||||
"cypress-file-upload": "^3.1.4",
|
||||
"cypress-file-upload": "^3.2.0",
|
||||
"cypress-plugin-retries": "^1.2.2",
|
||||
"dotenv": "^8.0.0",
|
||||
"faker": "Marak/faker.js#master",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:12.4-alpine as base
|
||||
FROM node:12.5-alpine as base
|
||||
LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
108
webapp/components/CategoriesSelect/CategoriesSelect.spec.js
Normal file
108
webapp/components/CategoriesSelect/CategoriesSelect.spec.js
Normal file
@ -0,0 +1,108 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import CategoriesSelect from './CategoriesSelect'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('CategoriesSelect.vue', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
let democracyAndPolitics
|
||||
let environmentAndNature
|
||||
let consumptionAndSustainablity
|
||||
|
||||
const categories = [
|
||||
{
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
},
|
||||
{
|
||||
id: 'cat4',
|
||||
name: 'Environment & Nature',
|
||||
icon: 'tree',
|
||||
},
|
||||
{
|
||||
id: 'cat15',
|
||||
name: 'Consumption & Sustainability',
|
||||
icon: 'shopping-cart',
|
||||
},
|
||||
{
|
||||
name: 'Cooperation & Development',
|
||||
icon: 'users',
|
||||
id: 'cat8',
|
||||
},
|
||||
]
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(CategoriesSelect, { mocks, localVue })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('toggleCategory', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.categories = categories
|
||||
democracyAndPolitics = wrapper.findAll('button').at(0)
|
||||
democracyAndPolitics.trigger('click')
|
||||
})
|
||||
|
||||
it('adds categories to selectedCategoryIds when clicked', () => {
|
||||
expect(wrapper.vm.selectedCategoryIds).toEqual([categories[0].id])
|
||||
})
|
||||
|
||||
it('emits an updateCategories event when the selectedCategoryIds changes', () => {
|
||||
expect(wrapper.emitted().updateCategories[0][0]).toEqual([categories[0].id])
|
||||
})
|
||||
|
||||
it('removes categories when clicked a second time', () => {
|
||||
democracyAndPolitics.trigger('click')
|
||||
expect(wrapper.vm.selectedCategoryIds).toEqual([])
|
||||
})
|
||||
|
||||
it('changes the selectedCount when selectedCategoryIds is updated', () => {
|
||||
expect(wrapper.vm.selectedCount).toEqual(1)
|
||||
democracyAndPolitics.trigger('click')
|
||||
expect(wrapper.vm.selectedCount).toEqual(0)
|
||||
})
|
||||
|
||||
it('sets a category to active when it has been selected', () => {
|
||||
expect(wrapper.vm.isActive(categories[0].id)).toEqual(true)
|
||||
})
|
||||
|
||||
describe('maximum', () => {
|
||||
beforeEach(() => {
|
||||
environmentAndNature = wrapper.findAll('button').at(1)
|
||||
consumptionAndSustainablity = wrapper.findAll('button').at(2)
|
||||
environmentAndNature.trigger('click')
|
||||
consumptionAndSustainablity.trigger('click')
|
||||
})
|
||||
|
||||
it('allows three categories to be selected', () => {
|
||||
expect(wrapper.vm.selectedCategoryIds).toEqual([
|
||||
categories[0].id,
|
||||
categories[1].id,
|
||||
categories[2].id,
|
||||
])
|
||||
})
|
||||
|
||||
it('sets reachedMaximum to true after three', () => {
|
||||
expect(wrapper.vm.reachedMaximum).toEqual(true)
|
||||
})
|
||||
|
||||
it('sets other categories to disabled after three', () => {
|
||||
expect(wrapper.vm.isDisabled(categories[3].id)).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
102
webapp/components/CategoriesSelect/CategoriesSelect.vue
Normal file
102
webapp/components/CategoriesSelect/CategoriesSelect.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'xx-small' }">
|
||||
<div v-for="category in categories" :key="category.id">
|
||||
<ds-flex-item>
|
||||
<ds-button
|
||||
size="small"
|
||||
@click.prevent="toggleCategory(category.id)"
|
||||
:primary="isActive(category.id)"
|
||||
:disabled="isDisabled(category.id)"
|
||||
>
|
||||
<ds-icon :name="category.icon" />
|
||||
{{ category.name }}
|
||||
</ds-button>
|
||||
</ds-flex-item>
|
||||
</div>
|
||||
</ds-flex>
|
||||
<p class="small-info">
|
||||
{{
|
||||
$t('contribution.categories.infoSelectedNoOfMaxCategories', {
|
||||
chosen: selectedCount,
|
||||
max: selectedMax,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
existingCategoryIds: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categories: null,
|
||||
selectedMax: 3,
|
||||
selectedCategoryIds: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedCount() {
|
||||
return this.selectedCategoryIds.length
|
||||
},
|
||||
reachedMaximum() {
|
||||
return this.selectedCount >= this.selectedMax
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedCategoryIds(categoryIds) {
|
||||
this.$emit('updateCategories', categoryIds)
|
||||
},
|
||||
existingCategoryIds: {
|
||||
immediate: true,
|
||||
handler: function(existingCategoryIds) {
|
||||
if (!existingCategoryIds || !existingCategoryIds.length) {
|
||||
return
|
||||
}
|
||||
this.selectedCategoryIds = existingCategoryIds
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCategory(id) {
|
||||
const index = this.selectedCategoryIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
this.selectedCategoryIds.splice(index, 1)
|
||||
} else {
|
||||
this.selectedCategoryIds.push(id)
|
||||
}
|
||||
},
|
||||
isActive(id) {
|
||||
const index = this.selectedCategoryIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
isDisabled(id) {
|
||||
return !!(this.reachedMaximum && !this.isActive(id))
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
Category: {
|
||||
query() {
|
||||
return gql(`{
|
||||
Category {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
}`)
|
||||
},
|
||||
result(result) {
|
||||
this.categories = result.data.Category
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,32 +1,53 @@
|
||||
<template>
|
||||
<div v-if="(comment.deleted || comment.disabled) && !isModerator">
|
||||
<ds-text style="padding-left: 40px; font-weight: bold;" color="soft">
|
||||
<ds-icon name="ban" />
|
||||
{{ this.$t('comment.content.unavailable-placeholder') }}
|
||||
</ds-text>
|
||||
<div v-if="(comment.deleted || comment.disabled) && !isModerator" :class="{ comment: true }">
|
||||
<ds-card>
|
||||
<ds-space margin-bottom="base" />
|
||||
<ds-text style="padding-left: 40px; font-weight: bold;" color="soft">
|
||||
<ds-icon name="ban" />
|
||||
{{ this.$t('comment.content.unavailable-placeholder') }}
|
||||
</ds-text>
|
||||
<ds-space margin-bottom="base" />
|
||||
</ds-card>
|
||||
</div>
|
||||
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
|
||||
<ds-space margin-bottom="x-small">
|
||||
<hc-user :user="author" :date-time="comment.createdAt" />
|
||||
</ds-space>
|
||||
<!-- Content Menu (can open Modals) -->
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
resource-type="comment"
|
||||
:resource="comment"
|
||||
:modalsData="menuModalsData"
|
||||
style="float-right"
|
||||
:is-owner="isAuthor(author.id)"
|
||||
/>
|
||||
</no-ssr>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<ds-space margin-bottom="small" />
|
||||
<div style="padding-left: 40px;" v-html="comment.contentExcerpt" />
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<ds-card>
|
||||
<ds-space margin-bottom="small">
|
||||
<hc-user :user="author" :date-time="comment.createdAt" />
|
||||
</ds-space>
|
||||
<!-- Content Menu (can open Modals) -->
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
resource-type="comment"
|
||||
:resource="comment"
|
||||
:modalsData="menuModalsData"
|
||||
style="float-right"
|
||||
:is-owner="isAuthor(author.id)"
|
||||
/>
|
||||
</no-ssr>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
|
||||
<div v-if="isCollapsed" v-html="comment.contentExcerpt" style="padding-left: 40px;" />
|
||||
<div
|
||||
v-show="comment.content !== comment.contentExcerpt"
|
||||
style="text-align: right; margin-right: 20px; margin-top: -12px;"
|
||||
>
|
||||
<a v-if="isCollapsed" style="padding-left: 40px;" @click="isCollapsed = !isCollapsed">
|
||||
{{ $t('comment.show.more') }}
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="!isCollapsed" v-html="comment.content" style="padding-left: 40px;" />
|
||||
<div style="text-align: right; margin-right: 20px; margin-top: -12px;">
|
||||
<a v-if="!isCollapsed" @click="isCollapsed = !isCollapsed" style="padding-left: 40px; ">
|
||||
{{ $t('comment.show.less') }}
|
||||
</a>
|
||||
</div>
|
||||
<ds-space margin-bottom="small" />
|
||||
</ds-card>
|
||||
</div>
|
||||
</template>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
@ -35,6 +56,11 @@ import HcUser from '~/components/User'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
|
||||
export default {
|
||||
data: function() {
|
||||
return {
|
||||
isCollapsed: true,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
HcUser,
|
||||
ContentMenu,
|
||||
|
||||
@ -46,9 +46,9 @@ export default {
|
||||
modalsData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
// default: () => {
|
||||
// return {}
|
||||
// },
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import ContributionForm from './index.vue'
|
||||
import ContributionForm from './ContributionForm.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
import TeaserImage from '~/components/TeaserImage/TeaserImage'
|
||||
|
||||
@ -28,7 +29,7 @@ describe('ContributionForm.vue', () => {
|
||||
file: { filename: 'avataar.svg', previewElement: '' },
|
||||
url: 'someUrlToImage',
|
||||
}
|
||||
|
||||
const image = '/uploads/1562010976466-avataaars'
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
@ -128,7 +129,9 @@ describe('ContributionForm.vue', () => {
|
||||
content: postContent,
|
||||
language: 'en',
|
||||
id: null,
|
||||
categoryIds: null,
|
||||
imageUpload: null,
|
||||
image: null,
|
||||
},
|
||||
}
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
@ -153,6 +156,14 @@ describe('ContributionForm.vue', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('supports adding categories', async () => {
|
||||
const categoryIds = ['cat12', 'cat15', 'cat37']
|
||||
expectedParams.variables.categoryIds = categoryIds
|
||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('supports adding a teaser image', async () => {
|
||||
expectedParams.variables.imageUpload = imageUpload
|
||||
wrapper.find(TeaserImage).vm.$emit('addTeaserImage', imageUpload)
|
||||
@ -205,7 +216,8 @@ describe('ContributionForm.vue', () => {
|
||||
title: 'dies ist ein Post',
|
||||
content: 'auf Deutsch geschrieben',
|
||||
language: 'de',
|
||||
imageUpload,
|
||||
image,
|
||||
categories: [{ id: 'cat12', name: 'Democracy & Politics' }],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -235,7 +247,9 @@ describe('ContributionForm.vue', () => {
|
||||
content: postContent,
|
||||
language: propsData.contribution.language,
|
||||
id: propsData.contribution.id,
|
||||
imageUpload,
|
||||
categoryIds: ['cat12'],
|
||||
image,
|
||||
imageUpload: null,
|
||||
},
|
||||
}
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
@ -244,6 +258,17 @@ describe('ContributionForm.vue', () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('supports updating categories', async () => {
|
||||
const categoryIds = ['cat3', 'cat51', 'cat37']
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
postTitleInput.setValue(postTitle)
|
||||
wrapper.vm.updateEditorContent(postContent)
|
||||
expectedParams.variables.categoryIds = categoryIds
|
||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -19,6 +19,11 @@
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space margin-bottom="xxx-large" />
|
||||
<hc-categories-select
|
||||
model="categoryIds"
|
||||
@updateCategories="updateCategories"
|
||||
:existingCategoryIds="form.categoryIds"
|
||||
/>
|
||||
<ds-flex class="contribution-form-footer">
|
||||
<ds-flex-item :width="{ base: '10%', sm: '10%', md: '10%', lg: '15%' }" />
|
||||
<ds-flex-item :width="{ base: '80%', sm: '30%', md: '30%', lg: '20%' }">
|
||||
@ -64,11 +69,13 @@ import HcEditor from '~/components/Editor/Editor'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import locales from '~/locales'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
import HcCategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||
import HcTeaserImage from '~/components/TeaserImage/TeaserImage'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEditor,
|
||||
HcCategoriesSelect,
|
||||
HcTeaserImage,
|
||||
},
|
||||
props: {
|
||||
@ -80,8 +87,10 @@ export default {
|
||||
title: '',
|
||||
content: '',
|
||||
teaserImage: null,
|
||||
image: null,
|
||||
language: null,
|
||||
languageOptions: [],
|
||||
categoryIds: null,
|
||||
},
|
||||
formSchema: {
|
||||
title: { required: true, min: 3, max: 64 },
|
||||
@ -106,7 +115,8 @@ export default {
|
||||
this.slug = contribution.slug
|
||||
this.form.content = contribution.content
|
||||
this.form.title = contribution.title
|
||||
this.form.teaserImage = contribution.imageUpload
|
||||
this.form.image = contribution.image
|
||||
this.form.categoryIds = this.categoryIds(contribution.categories)
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -124,7 +134,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
const { title, content, teaserImage } = this.form
|
||||
const { title, content, image, teaserImage, categoryIds } = this.form
|
||||
let language
|
||||
if (this.form.language) {
|
||||
language = this.form.language.value
|
||||
@ -141,7 +151,9 @@ export default {
|
||||
id: this.id,
|
||||
title,
|
||||
content,
|
||||
categoryIds,
|
||||
language,
|
||||
image,
|
||||
imageUpload: teaserImage,
|
||||
},
|
||||
})
|
||||
@ -149,7 +161,6 @@ export default {
|
||||
this.loading = false
|
||||
this.$toast.success(this.$t('contribution.success'))
|
||||
this.disabled = true
|
||||
|
||||
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost']
|
||||
|
||||
this.$router.push({
|
||||
@ -172,9 +183,19 @@ export default {
|
||||
this.form.languageOptions.push({ label: locale.name, value: locale.code })
|
||||
})
|
||||
},
|
||||
updateCategories(ids) {
|
||||
this.form.categoryIds = ids
|
||||
},
|
||||
addTeaserImage(file) {
|
||||
this.form.teaserImage = file
|
||||
},
|
||||
categoryIds(categories) {
|
||||
let categoryIds = []
|
||||
categories.map(categoryId => {
|
||||
categoryIds.push(categoryId.id)
|
||||
})
|
||||
return categoryIds
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
User: {
|
||||
@ -89,8 +89,19 @@ export default {
|
||||
}, 500)
|
||||
}, 1500)
|
||||
} catch (err) {
|
||||
this.$emit('close')
|
||||
this.success = false
|
||||
this.$toast.error(err.message)
|
||||
switch (err.message) {
|
||||
case 'GraphQL error: User':
|
||||
this.$toast.error(this.$t('report.user.error'))
|
||||
break
|
||||
case 'GraphQL error: Post':
|
||||
this.$toast.error(this.$t('report.contribution.error'))
|
||||
break
|
||||
case 'GraphQL error: Comment':
|
||||
this.$toast.error(this.$t('report.comment.error'))
|
||||
break
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@ -10,9 +10,7 @@
|
||||
>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<a v-if="isActive" class="search-clear-btn" @click="clear">
|
||||
|
||||
</a>
|
||||
<a v-if="isActive" class="search-clear-btn" @click="clear"> </a>
|
||||
<ds-select
|
||||
:id="id"
|
||||
ref="input"
|
||||
@ -41,9 +39,7 @@
|
||||
<template slot="option" slot-scope="{ option }">
|
||||
<ds-flex>
|
||||
<ds-flex-item class="search-option-label">
|
||||
<ds-text>
|
||||
{{ option.label | truncate(70) }}
|
||||
</ds-text>
|
||||
<ds-text>{{ option.label | truncate(70) }}</ds-text>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item class="search-option-meta" width="280px">
|
||||
<ds-flex>
|
||||
|
||||
@ -3,10 +3,11 @@ import gql from 'graphql-tag'
|
||||
export default () => {
|
||||
return {
|
||||
CreateComment: gql`
|
||||
mutation($postId: ID, $content: String!) {
|
||||
mutation($postId: ID!, $content: String!) {
|
||||
CreateComment(postId: $postId, content: $content) {
|
||||
id
|
||||
contentExcerpt
|
||||
content
|
||||
author {
|
||||
id
|
||||
slug
|
||||
|
||||
@ -8,6 +8,7 @@ export default i18n => {
|
||||
comments(orderBy: createdAt_asc) {
|
||||
id
|
||||
contentExcerpt
|
||||
content
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
|
||||
@ -3,20 +3,25 @@ import gql from 'graphql-tag'
|
||||
export default () => {
|
||||
return {
|
||||
CreatePost: gql`
|
||||
mutation($title: String!, $content: String!, $language: String, $imageUpload: Upload) {
|
||||
mutation(
|
||||
$title: String!
|
||||
$content: String!
|
||||
$language: String
|
||||
$categoryIds: [ID]
|
||||
$imageUpload: Upload
|
||||
) {
|
||||
CreatePost(
|
||||
title: $title
|
||||
content: $content
|
||||
language: $language
|
||||
categoryIds: $categoryIds
|
||||
imageUpload: $imageUpload
|
||||
) {
|
||||
id
|
||||
title
|
||||
slug
|
||||
content
|
||||
contentExcerpt
|
||||
language
|
||||
imageUpload
|
||||
}
|
||||
}
|
||||
`,
|
||||
@ -27,6 +32,8 @@ export default () => {
|
||||
$content: String!
|
||||
$language: String
|
||||
$imageUpload: Upload
|
||||
$categoryIds: [ID]
|
||||
$image: String
|
||||
) {
|
||||
UpdatePost(
|
||||
id: $id
|
||||
@ -34,6 +41,8 @@ export default () => {
|
||||
content: $content
|
||||
language: $language
|
||||
imageUpload: $imageUpload
|
||||
categoryIds: $categoryIds
|
||||
image: $image
|
||||
) {
|
||||
id
|
||||
title
|
||||
@ -41,7 +50,7 @@ export default () => {
|
||||
content
|
||||
contentExcerpt
|
||||
language
|
||||
imageUpload
|
||||
image
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
79
webapp/graphql/PostQuery.js
Normal file
79
webapp/graphql/PostQuery.js
Normal file
@ -0,0 +1,79 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql(`
|
||||
query Post($slug: String!) {
|
||||
Post(slug: $slug) {
|
||||
id
|
||||
title
|
||||
content
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
slug
|
||||
image
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
avatar
|
||||
disabled
|
||||
deleted
|
||||
shoutedCount
|
||||
contributionsCount
|
||||
commentsCount
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
location {
|
||||
name: name${lang}
|
||||
}
|
||||
badges {
|
||||
id
|
||||
key
|
||||
icon
|
||||
}
|
||||
}
|
||||
tags {
|
||||
name
|
||||
}
|
||||
commentsCount
|
||||
comments(orderBy: createdAt_desc) {
|
||||
id
|
||||
contentExcerpt
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
avatar
|
||||
disabled
|
||||
deleted
|
||||
shoutedCount
|
||||
contributionsCount
|
||||
commentsCount
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
location {
|
||||
name: name${lang}
|
||||
}
|
||||
badges {
|
||||
id
|
||||
key
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
shoutedCount
|
||||
shoutedByCurrentUser
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -1,88 +1,98 @@
|
||||
<template>
|
||||
<div class="layout-default">
|
||||
<div class="main-navigation">
|
||||
<ds-container class="main-navigation-container">
|
||||
<div class="main-navigation-left">
|
||||
<a v-router-link style="display: inline-flex" href="/">
|
||||
<ds-logo />
|
||||
</a>
|
||||
</div>
|
||||
<div class="main-navigation-center hc-navbar-search">
|
||||
<search-input
|
||||
id="nav-search"
|
||||
:delay="300"
|
||||
:pending="quickSearchPending"
|
||||
:results="quickSearchResults"
|
||||
@clear="quickSearchClear"
|
||||
@search="value => quickSearch({ value })"
|
||||
@select="goToPost"
|
||||
/>
|
||||
</div>
|
||||
<div class="main-navigation-right">
|
||||
<no-ssr>
|
||||
<locale-switch class="topbar-locale-switch" placement="bottom" offset="23" />
|
||||
</no-ssr>
|
||||
<template v-if="isLoggedIn">
|
||||
<no-ssr>
|
||||
<notification-menu />
|
||||
</no-ssr>
|
||||
<no-ssr>
|
||||
<dropdown class="avatar-menu">
|
||||
<template slot="default" slot-scope="{ toggleMenu }">
|
||||
<a
|
||||
class="avatar-menu-trigger"
|
||||
:href="
|
||||
$router.resolve({
|
||||
name: 'profile-id-slug',
|
||||
params: { id: user.id, slug: user.slug },
|
||||
}).href
|
||||
"
|
||||
@click.prevent="toggleMenu"
|
||||
>
|
||||
<hc-avatar :user="user" />
|
||||
<ds-icon size="xx-small" name="angle-down" />
|
||||
</a>
|
||||
<ds-container class="main-navigation-container" style="padding: 10px 10px;">
|
||||
<div>
|
||||
<ds-flex>
|
||||
<ds-flex-item :width="{ base: '49px', md: '150px' }">
|
||||
<a v-router-link style="display: inline-flex" href="/">
|
||||
<ds-logo />
|
||||
</a>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<div id="nav-search-box" v-on:click="unfolded" @blur.capture="foldedup">
|
||||
<search-input
|
||||
id="nav-search"
|
||||
:delay="300"
|
||||
:pending="quickSearchPending"
|
||||
:results="quickSearchResults"
|
||||
@clear="quickSearchClear"
|
||||
@search="value => quickSearch({ value })"
|
||||
@select="goToPost"
|
||||
/>
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item width="200px" style="background-color:white">
|
||||
<div class="main-navigation-right" style="float:right">
|
||||
<no-ssr>
|
||||
<locale-switch class="topbar-locale-switch" placement="bottom" offset="23" />
|
||||
</no-ssr>
|
||||
<template v-if="isLoggedIn">
|
||||
<no-ssr>
|
||||
<notification-menu />
|
||||
</no-ssr>
|
||||
<no-ssr>
|
||||
<dropdown class="avatar-menu">
|
||||
<template slot="default" slot-scope="{ toggleMenu }">
|
||||
<a
|
||||
class="avatar-menu-trigger"
|
||||
:href="
|
||||
$router.resolve({
|
||||
name: 'profile-id-slug',
|
||||
params: { id: user.id, slug: user.slug },
|
||||
}).href
|
||||
"
|
||||
@click.prevent="toggleMenu"
|
||||
>
|
||||
<hc-avatar :user="user" />
|
||||
<ds-icon size="xx-small" name="angle-down" />
|
||||
</a>
|
||||
</template>
|
||||
<template slot="popover" slot-scope="{ closeMenu }">
|
||||
<div class="avatar-menu-popover">
|
||||
{{ $t('login.hello') }}
|
||||
<b>{{ userName }}</b>
|
||||
<template v-if="user.role !== 'user'">
|
||||
<ds-text color="softer" size="small" style="margin-bottom: 0">
|
||||
{{ user.role | camelCase }}
|
||||
</ds-text>
|
||||
</template>
|
||||
<hr />
|
||||
<ds-menu :routes="routes" :matcher="matcher">
|
||||
<ds-menu-item
|
||||
slot="menuitem"
|
||||
slot-scope="item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.native="closeMenu(false)"
|
||||
>
|
||||
<ds-icon :name="item.route.icon" />
|
||||
{{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
<hr />
|
||||
<nuxt-link class="logout-link" :to="{ name: 'logout' }">
|
||||
<ds-icon name="sign-out" />
|
||||
{{ $t('login.logout') }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
</no-ssr>
|
||||
</template>
|
||||
<template slot="popover" slot-scope="{ closeMenu }">
|
||||
<div class="avatar-menu-popover">
|
||||
{{ $t('login.hello') }}
|
||||
<b>{{ userName }}</b>
|
||||
<template v-if="user.role !== 'user'">
|
||||
<ds-text color="softer" size="small" style="margin-bottom: 0">
|
||||
{{ user.role | camelCase }}
|
||||
</ds-text>
|
||||
</template>
|
||||
<hr />
|
||||
<ds-menu :routes="routes" :matcher="matcher">
|
||||
<ds-menu-item
|
||||
slot="menuitem"
|
||||
slot-scope="item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.native="closeMenu(false)"
|
||||
>
|
||||
<ds-icon :name="item.route.icon" />
|
||||
{{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
<hr />
|
||||
<nuxt-link class="logout-link" :to="{ name: 'logout' }">
|
||||
<ds-icon name="sign-out" />
|
||||
{{ $t('login.logout') }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
</no-ssr>
|
||||
</template>
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</div>
|
||||
</ds-container>
|
||||
</div>
|
||||
|
||||
<ds-container>
|
||||
<div style="padding: 6rem 2rem 5rem;">
|
||||
<nuxt />
|
||||
</div>
|
||||
</ds-container>
|
||||
|
||||
<div id="overlay" />
|
||||
<no-ssr>
|
||||
<modal />
|
||||
@ -181,9 +191,23 @@ export default {
|
||||
}
|
||||
return this.$route.path.indexOf(url) === 0
|
||||
},
|
||||
unfolded: function() {
|
||||
document.getElementById('nav-search-box').classList.add('unfolded')
|
||||
},
|
||||
foldedup: function() {
|
||||
document.getElementById('nav-search-box').classList.remove('unfolded')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.unfolded {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.topbar-locale-switch {
|
||||
@ -199,28 +223,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.main-navigation-container {
|
||||
padding: $space-x-small $space-large !important;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.main-navigation-left {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main-navigation-center {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
padding-right: $space-large;
|
||||
padding-left: $space-large;
|
||||
}
|
||||
|
||||
.main-navigation-right {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
@ -197,6 +197,10 @@
|
||||
"menu": {
|
||||
"edit": "Kommentar bearbeiten",
|
||||
"delete": "Kommentar löschen"
|
||||
},
|
||||
"show": {
|
||||
"more": "mehr anzeigen",
|
||||
"less": "weniger anzeigen"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
@ -316,17 +320,20 @@
|
||||
"user": {
|
||||
"title": "Nutzer freigeben",
|
||||
"type": "Nutzer",
|
||||
"message": "Bist du sicher, dass du den Nutzer \"<b>{name}</b>\" freigeben möchtest?"
|
||||
"message": "Bist du sicher, dass du den Nutzer \"<b>{name}</b>\" freigeben möchtest?",
|
||||
"error": "Den User hast du schon gemeldet!"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Beitrag freigeben",
|
||||
"type": "Beitrag",
|
||||
"message": "Bist du sicher, dass du den Beitrag \"<b>{name}</b>\" freigeben möchtest?"
|
||||
"message": "Bist du sicher, dass du den Beitrag \"<b>{name}</b>\" freigeben möchtest?",
|
||||
"error": " Den Beitrag hast du schon gemeldet!"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Kommentar freigeben",
|
||||
"type": "Kommentar",
|
||||
"message": "Bist du sicher, dass du den Kommentar \"<b>{name}</b>\" freigeben möchtest?"
|
||||
"message": "Bist du sicher, dass du den Kommentar \"<b>{name}</b>\" freigeben möchtest?",
|
||||
"error": "Den Kommentar hast du schon gemeldet!"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
@ -339,7 +346,10 @@
|
||||
"filterFollow": "Beiträge filtern von Usern denen ich folge",
|
||||
"filterALL": "Alle Beiträge anzeigen",
|
||||
"success": "Gespeichert!",
|
||||
"languageSelectLabel": "Sprache"
|
||||
"languageSelectLabel": "Sprache",
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Kategorien ausgewählt"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -197,7 +197,12 @@
|
||||
"menu": {
|
||||
"edit": "Edit Comment",
|
||||
"delete": "Delete Comment"
|
||||
},
|
||||
"show": {
|
||||
"more": "show more",
|
||||
"less": "show less"
|
||||
}
|
||||
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
@ -288,17 +293,20 @@
|
||||
"user": {
|
||||
"title": "Report User",
|
||||
"type": "User",
|
||||
"message": "Do you really want to report the user \"<b>{name}</b>\"?"
|
||||
"message": "Do you really want to report the user \"<b>{name}</b>\"?",
|
||||
"error": "You already reported the user!"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Report Post",
|
||||
"type": "Contribution",
|
||||
"message": "Do you really want to report the contribution \"<b>{name}</b>\"?"
|
||||
"message": "Do you really want to report the contribution \"<b>{name}</b>\"?",
|
||||
"error": "You have already reported the contribution!"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Report Comment",
|
||||
"type": "Comment",
|
||||
"message": "Do you really want to report the comment from \"<b>{name}</b>\"?"
|
||||
"message": "Do you really want to report the comment from \"<b>{name}</b>\"?",
|
||||
"error": "You have already reported the comment!"
|
||||
}
|
||||
},
|
||||
"followButton": {
|
||||
@ -338,6 +346,9 @@
|
||||
"filterFollow": "Filter contributions from users I follow",
|
||||
"filterALL": "View all contributions",
|
||||
"success": "Saved!",
|
||||
"languageSelectLabel": "Language"
|
||||
"languageSelectLabel": "Language",
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,17 +229,20 @@
|
||||
"user": {
|
||||
"title": "Signaler l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
"message": "Souhaitez-vous vraiment signaler l'utilisateur \" <b> {name} </b> \"?"
|
||||
"message": "Souhaitez-vous vraiment signaler l'utilisateur \" <b> {name} </b> \"?",
|
||||
"error": "Vous avez déjà signalé l'utilisateur!"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Signaler l'entrée",
|
||||
"type": "Apport",
|
||||
"message": "Souhaitez-vous vraiment signaler l'entrée\" <b> {name} </b> \"?"
|
||||
"message": "Souhaitez-vous vraiment signaler l'entrée\" <b> {name} </b> \"?",
|
||||
"error": "Vous avez déjà rapporté la contribution!"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Signaler un commentaire",
|
||||
"type": "Commentaire",
|
||||
"message": "Souhaitez-vous vraiment signaler l'utilisateur \" <b> {name} </b> \"?"
|
||||
"message": "Souhaitez-vous vraiment signaler l'utilisateur \" <b> {name} </b> \"?",
|
||||
"error": "Vous avez déjà rapporté le commentaire!"
|
||||
}
|
||||
},
|
||||
"followButton": {
|
||||
|
||||
@ -62,8 +62,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import HcCategory from '~/components/Category'
|
||||
import HcTag from '~/components/Tag'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
@ -72,6 +70,7 @@ import HcShoutButton from '~/components/ShoutButton.vue'
|
||||
import HcCommentForm from '~/components/comments/CommentForm'
|
||||
import HcCommentList from '~/components/comments/CommentList'
|
||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||
import PostQuery from '~/graphql/PostQuery.js'
|
||||
|
||||
export default {
|
||||
name: 'PostSlug',
|
||||
@ -113,80 +112,7 @@ export default {
|
||||
app: { apolloProvider, $i18n },
|
||||
} = context
|
||||
const client = apolloProvider.defaultClient
|
||||
const query = gql(`
|
||||
query Post($slug: String!) {
|
||||
Post(slug: $slug) {
|
||||
id
|
||||
title
|
||||
content
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
slug
|
||||
image
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
avatar
|
||||
disabled
|
||||
deleted
|
||||
shoutedCount
|
||||
contributionsCount
|
||||
commentsCount
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
location {
|
||||
name: name${$i18n.locale().toUpperCase()}
|
||||
}
|
||||
badges {
|
||||
id
|
||||
key
|
||||
icon
|
||||
}
|
||||
}
|
||||
tags {
|
||||
name
|
||||
}
|
||||
commentsCount
|
||||
comments(orderBy: createdAt_desc) {
|
||||
id
|
||||
contentExcerpt
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
avatar
|
||||
disabled
|
||||
deleted
|
||||
shoutedCount
|
||||
contributionsCount
|
||||
commentsCount
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
location {
|
||||
name: name${$i18n.locale().toUpperCase()}
|
||||
}
|
||||
badges {
|
||||
id
|
||||
key
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
shoutedCount
|
||||
shoutedByCurrentUser
|
||||
}
|
||||
}
|
||||
`)
|
||||
const query = PostQuery($i18n)
|
||||
const variables = { slug: params.slug }
|
||||
const {
|
||||
data: { Post },
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcContributionForm from '~/components/ContributionForm'
|
||||
import HcContributionForm from '~/components/ContributionForm/ContributionForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import HcContributionForm from '~/components/ContributionForm'
|
||||
import HcContributionForm from '~/components/ContributionForm/ContributionForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@ -1822,10 +1822,10 @@ cypress-cucumber-preprocessor@^1.12.0:
|
||||
glob "^7.1.2"
|
||||
through "^2.3.8"
|
||||
|
||||
cypress-file-upload@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.1.4.tgz#cc208cb937a3abb136b52309eaf4637d5676c5bd"
|
||||
integrity sha512-4aZeJOYFhYiP+nk9Mo5YHWqComsT24J9OBQVJzvkEzw7g1v2ogGe7nLT/U7Fsm/Xjl1Tyxsc0xxECa254WfQqg==
|
||||
cypress-file-upload@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.2.0.tgz#a48282e1fea385ba6aef9ec3296a934026f5fd67"
|
||||
integrity sha512-C1nFgURTgvtz9MpP7sYKjhKSdgQvDhUs3f4w6hvEH33wDDQUkmXwrozKDvxXSdccc07M7wH4O5JF61sTkvY8lA==
|
||||
|
||||
cypress-plugin-retries@^1.2.2:
|
||||
version "1.2.2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user