mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #1840 from Human-Connection/1205-pinned-post-for-admins
Allow admins to pin a post
This commit is contained in:
commit
46fd534a28
@ -134,6 +134,7 @@ const permissions = shield(
|
||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||
blockedUsers: isAuthenticated,
|
||||
notifications: isAuthenticated,
|
||||
profilePagePosts: or(onlyEnabledContent, isModerator),
|
||||
},
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
@ -174,6 +175,8 @@ const permissions = shield(
|
||||
markAsRead: isAuthenticated,
|
||||
AddEmailAddress: isAuthenticated,
|
||||
VerifyEmailAddress: isAuthenticated,
|
||||
pinPost: isAdmin,
|
||||
unpinPost: isAdmin,
|
||||
},
|
||||
User: {
|
||||
email: or(isMyOwn, isAdmin),
|
||||
|
||||
@ -114,6 +114,15 @@ module.exports = {
|
||||
target: 'Location',
|
||||
direction: 'out',
|
||||
},
|
||||
pinned: {
|
||||
type: 'relationship',
|
||||
relationship: 'PINNED',
|
||||
target: 'Post',
|
||||
direction: 'out',
|
||||
properties: {
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
allowEmbedIframes: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
|
||||
@ -86,6 +86,7 @@ export default function Resolver(type, options = {}) {
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
const result = {
|
||||
...undefinedToNullResolver(undefinedToNull),
|
||||
...booleanResolver(boolean),
|
||||
|
||||
@ -2,10 +2,9 @@ import uuid from 'uuid/v4'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import fileUpload from './fileUpload'
|
||||
import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
||||
import { mergeWith, isArray } from 'lodash'
|
||||
import { mergeWith, isArray, isEmpty } from 'lodash'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const filterForBlockedUsers = async (params, context) => {
|
||||
if (!context.user) return params
|
||||
const [blockedUsers, blockedByUsers] = await Promise.all([
|
||||
@ -29,16 +28,31 @@ const filterForBlockedUsers = async (params, context) => {
|
||||
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 {
|
||||
Query: {
|
||||
Post: async (object, params, context, resolveInfo) => {
|
||||
params = await filterForBlockedUsers(params, context)
|
||||
params = await maintainPinnedPosts(params)
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
},
|
||||
findPosts: async (object, params, context, resolveInfo) => {
|
||||
params = await filterForBlockedUsers(params, context)
|
||||
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) => {
|
||||
const session = context.driver.session()
|
||||
const { postId, data } = params
|
||||
@ -115,10 +129,10 @@ export default {
|
||||
delete params.categoryIds
|
||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||
const session = context.driver.session()
|
||||
|
||||
let updatePostCypher = `MATCH (post:Post {id: $params.id})
|
||||
SET post += $params
|
||||
SET post.updatedAt = toString(datetime())
|
||||
WITH post
|
||||
`
|
||||
|
||||
if (categoryIds && categoryIds.length) {
|
||||
@ -131,10 +145,10 @@ export default {
|
||||
await session.run(cypherDeletePreviousRelations, { params })
|
||||
|
||||
updatePostCypher += `
|
||||
WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (post)-[:CATEGORIZED]->(category)
|
||||
WITH post
|
||||
`
|
||||
}
|
||||
|
||||
@ -211,10 +225,75 @@ export default {
|
||||
})
|
||||
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: {
|
||||
...Resolver('Post', {
|
||||
undefinedToNull: ['activityId', 'objectId', 'image', 'language'],
|
||||
undefinedToNull: ['activityId', 'objectId', 'image', 'language', 'pinnedAt'],
|
||||
hasMany: {
|
||||
tags: '-[:TAGGED]->(related:Tag)',
|
||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||
@ -225,6 +304,7 @@ export default {
|
||||
hasOne: {
|
||||
author: '<-[:WROTE]-(related:User)',
|
||||
disabledBy: '<-[:DISABLED]-(related:User)',
|
||||
pinnedBy: '<-[:PINNED]-(related:User)',
|
||||
},
|
||||
count: {
|
||||
commentsCount:
|
||||
|
||||
@ -39,7 +39,8 @@ const createPostMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
await factory.cleanDatabase()
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
@ -269,7 +270,10 @@ describe('CreatePost', () => {
|
||||
})
|
||||
|
||||
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(
|
||||
expected,
|
||||
)
|
||||
@ -285,6 +289,7 @@ describe('CreatePost', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
}
|
||||
await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject(
|
||||
expected,
|
||||
@ -366,7 +371,12 @@ describe('UpdatePost', () => {
|
||||
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
title
|
||||
content
|
||||
author {
|
||||
name
|
||||
slug
|
||||
}
|
||||
categories {
|
||||
id
|
||||
}
|
||||
@ -386,7 +396,6 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
variables = {
|
||||
...variables,
|
||||
id: 'p9876',
|
||||
title: 'New title',
|
||||
content: 'New content',
|
||||
@ -395,8 +404,11 @@ describe('UpdatePost', () => {
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: updatePostMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
authenticatedUser = null
|
||||
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', () => {
|
||||
|
||||
@ -16,6 +16,10 @@ type Post {
|
||||
createdAt: String
|
||||
updatedAt: 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]!
|
||||
@cypher(
|
||||
statement: """
|
||||
@ -40,7 +44,7 @@ type Post {
|
||||
@cypher(
|
||||
statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
|
||||
)
|
||||
|
||||
|
||||
# Has the currently logged in user shouted that post?
|
||||
shoutedByCurrentUser: Boolean!
|
||||
@cypher(
|
||||
@ -84,9 +88,12 @@ type Mutation {
|
||||
DeletePost(id: ID!): Post
|
||||
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||
RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||
pinPost(id: ID!): Post
|
||||
unpinPost(id: ID!): Post
|
||||
}
|
||||
|
||||
type Query {
|
||||
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
||||
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
||||
profilePagePosts(filter: _PostFilter, first: Int, offset: Int, orderBy: [_PostOrdering]): [Post]
|
||||
}
|
||||
|
||||
@ -1,182 +1,181 @@
|
||||
type User {
|
||||
id: ID!
|
||||
actorId: String
|
||||
name: String
|
||||
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
|
||||
slug: String!
|
||||
avatar: String
|
||||
coverImg: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
role: UserGroup!
|
||||
publicKey: String
|
||||
invitedBy: User @relation(name: "INVITED", direction: "IN")
|
||||
invited: [User] @relation(name: "INVITED", direction: "OUT")
|
||||
id: ID!
|
||||
actorId: String
|
||||
name: String
|
||||
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
|
||||
slug: String!
|
||||
avatar: String
|
||||
coverImg: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
role: UserGroup!
|
||||
publicKey: String
|
||||
invitedBy: User @relation(name: "INVITED", direction: "IN")
|
||||
invited: [User] @relation(name: "INVITED", direction: "OUT")
|
||||
|
||||
location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l")
|
||||
locationName: String
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
|
||||
location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l")
|
||||
locationName: String
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
|
||||
|
||||
# createdAt: DateTime
|
||||
# updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
# createdAt: DateTime
|
||||
# updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
termsAndConditionsAgreedVersion: String
|
||||
termsAndConditionsAgreedAt: String
|
||||
termsAndConditionsAgreedVersion: String
|
||||
termsAndConditionsAgreedAt: String
|
||||
|
||||
allowEmbedIframes: Boolean
|
||||
allowEmbedIframes: Boolean
|
||||
locale: String
|
||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
locale: String
|
||||
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
|
||||
followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
|
||||
followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)")
|
||||
# Is the currently logged in user following that user?
|
||||
followedByCurrentUser: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
isBlocked: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
|
||||
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)")
|
||||
# contributions: [WrittenPost]!
|
||||
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||
# @cypher(
|
||||
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
|
||||
# )
|
||||
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
|
||||
contributionsCount: Int! @cypher(
|
||||
statement: """
|
||||
MATCH (this)-[: WROTE]->(r: Post)
|
||||
WHERE NOT r.deleted = true AND NOT r.disabled = true
|
||||
RETURN COUNT(r)
|
||||
"""
|
||||
)
|
||||
|
||||
# Is the currently logged in user following that user?
|
||||
followedByCurrentUser: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
isBlocked: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||
commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
||||
|
||||
# contributions: [WrittenPost]!
|
||||
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||
# @cypher(
|
||||
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
|
||||
# )
|
||||
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
|
||||
contributionsCount: Int! @cypher(
|
||||
statement: """
|
||||
MATCH (this)-[: WROTE]->(r: Post)
|
||||
WHERE NOT r.deleted = true AND NOT r.disabled = true
|
||||
RETURN COUNT(r)
|
||||
"""
|
||||
)
|
||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
|
||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||
commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
||||
badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)")
|
||||
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
||||
badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)")
|
||||
|
||||
emotions: [EMOTED]
|
||||
emotions: [EMOTED]
|
||||
}
|
||||
|
||||
|
||||
input _UserFilter {
|
||||
AND: [_UserFilter!]
|
||||
OR: [_UserFilter!]
|
||||
name_contains: String
|
||||
about_contains: String
|
||||
slug_contains: String
|
||||
id: ID
|
||||
id_not: ID
|
||||
id_in: [ID!]
|
||||
id_not_in: [ID!]
|
||||
id_contains: ID
|
||||
id_not_contains: ID
|
||||
id_starts_with: ID
|
||||
id_not_starts_with: ID
|
||||
id_ends_with: ID
|
||||
id_not_ends_with: ID
|
||||
friends: _UserFilter
|
||||
friends_not: _UserFilter
|
||||
friends_in: [_UserFilter!]
|
||||
friends_not_in: [_UserFilter!]
|
||||
friends_some: _UserFilter
|
||||
friends_none: _UserFilter
|
||||
friends_single: _UserFilter
|
||||
friends_every: _UserFilter
|
||||
following: _UserFilter
|
||||
following_not: _UserFilter
|
||||
following_in: [_UserFilter!]
|
||||
following_not_in: [_UserFilter!]
|
||||
following_some: _UserFilter
|
||||
following_none: _UserFilter
|
||||
following_single: _UserFilter
|
||||
following_every: _UserFilter
|
||||
followedBy: _UserFilter
|
||||
followedBy_not: _UserFilter
|
||||
followedBy_in: [_UserFilter!]
|
||||
followedBy_not_in: [_UserFilter!]
|
||||
followedBy_some: _UserFilter
|
||||
followedBy_none: _UserFilter
|
||||
followedBy_single: _UserFilter
|
||||
followedBy_every: _UserFilter
|
||||
AND: [_UserFilter!]
|
||||
OR: [_UserFilter!]
|
||||
name_contains: String
|
||||
about_contains: String
|
||||
slug_contains: String
|
||||
id: ID
|
||||
id_not: ID
|
||||
id_in: [ID!]
|
||||
id_not_in: [ID!]
|
||||
id_contains: ID
|
||||
id_not_contains: ID
|
||||
id_starts_with: ID
|
||||
id_not_starts_with: ID
|
||||
id_ends_with: ID
|
||||
id_not_ends_with: ID
|
||||
friends: _UserFilter
|
||||
friends_not: _UserFilter
|
||||
friends_in: [_UserFilter!]
|
||||
friends_not_in: [_UserFilter!]
|
||||
friends_some: _UserFilter
|
||||
friends_none: _UserFilter
|
||||
friends_single: _UserFilter
|
||||
friends_every: _UserFilter
|
||||
following: _UserFilter
|
||||
following_not: _UserFilter
|
||||
following_in: [_UserFilter!]
|
||||
following_not_in: [_UserFilter!]
|
||||
following_some: _UserFilter
|
||||
following_none: _UserFilter
|
||||
following_single: _UserFilter
|
||||
following_every: _UserFilter
|
||||
followedBy: _UserFilter
|
||||
followedBy_not: _UserFilter
|
||||
followedBy_in: [_UserFilter!]
|
||||
followedBy_not_in: [_UserFilter!]
|
||||
followedBy_some: _UserFilter
|
||||
followedBy_none: _UserFilter
|
||||
followedBy_single: _UserFilter
|
||||
followedBy_every: _UserFilter
|
||||
role_in: [UserGroup!]
|
||||
}
|
||||
|
||||
type Query {
|
||||
User(
|
||||
id: ID
|
||||
email: String
|
||||
actorId: String
|
||||
name: String
|
||||
slug: String
|
||||
avatar: String
|
||||
coverImg: String
|
||||
role: UserGroup
|
||||
locationName: String
|
||||
about: String
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
friendsCount: Int
|
||||
followingCount: Int
|
||||
followedByCount: Int
|
||||
followedByCurrentUser: Boolean
|
||||
contributionsCount: Int
|
||||
commentedCount: Int
|
||||
shoutedCount: Int
|
||||
badgesCount: Int
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_UserOrdering]
|
||||
filter: _UserFilter
|
||||
): [User]
|
||||
User(
|
||||
id: ID
|
||||
email: String
|
||||
actorId: String
|
||||
name: String
|
||||
slug: String
|
||||
avatar: String
|
||||
coverImg: String
|
||||
role: UserGroup
|
||||
locationName: String
|
||||
about: String
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
friendsCount: Int
|
||||
followingCount: Int
|
||||
followedByCount: Int
|
||||
followedByCurrentUser: Boolean
|
||||
contributionsCount: Int
|
||||
commentedCount: Int
|
||||
shoutedCount: Int
|
||||
badgesCount: Int
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_UserOrdering]
|
||||
filter: _UserFilter
|
||||
): [User]
|
||||
|
||||
blockedUsers: [User]
|
||||
currentUser: User
|
||||
blockedUsers: [User]
|
||||
currentUser: User
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
UpdateUser (
|
||||
id: ID!
|
||||
name: String
|
||||
email: String
|
||||
slug: String
|
||||
avatar: String
|
||||
coverImg: String
|
||||
avatarUpload: Upload
|
||||
locationName: String
|
||||
about: String
|
||||
termsAndConditionsAgreedVersion: String
|
||||
termsAndConditionsAgreedAt: String
|
||||
allowEmbedIframes: Boolean
|
||||
UpdateUser (
|
||||
id: ID!
|
||||
name: String
|
||||
email: String
|
||||
slug: String
|
||||
avatar: String
|
||||
coverImg: String
|
||||
avatarUpload: Upload
|
||||
locationName: String
|
||||
about: String
|
||||
termsAndConditionsAgreedVersion: String
|
||||
termsAndConditionsAgreedAt: String
|
||||
allowEmbedIframes: Boolean
|
||||
locale: String
|
||||
): User
|
||||
): User
|
||||
|
||||
DeleteUser(id: ID!, resource: [Deletable]): User
|
||||
DeleteUser(id: ID!, resource: [Deletable]): User
|
||||
|
||||
|
||||
block(id: ID!): User
|
||||
unblock(id: ID!): User
|
||||
block(id: ID!): User
|
||||
unblock(id: ID!): User
|
||||
}
|
||||
|
||||
@ -55,24 +55,46 @@ export default {
|
||||
routes() {
|
||||
let routes = []
|
||||
|
||||
if (this.isOwner && this.resourceType === 'contribution') {
|
||||
routes.push({
|
||||
name: this.$t(`post.menu.edit`),
|
||||
path: this.$router.resolve({
|
||||
name: 'post-edit-id',
|
||||
params: {
|
||||
id: this.resource.id,
|
||||
if (this.resourceType === 'contribution') {
|
||||
if (this.isOwner) {
|
||||
routes.push({
|
||||
name: this.$t(`post.menu.edit`),
|
||||
path: this.$router.resolve({
|
||||
name: 'post-edit-id',
|
||||
params: {
|
||||
id: this.resource.id,
|
||||
},
|
||||
}).href,
|
||||
icon: 'edit',
|
||||
})
|
||||
routes.push({
|
||||
name: this.$t(`post.menu.delete`),
|
||||
callback: () => {
|
||||
this.openModal('delete')
|
||||
},
|
||||
}).href,
|
||||
icon: 'edit',
|
||||
})
|
||||
routes.push({
|
||||
name: this.$t(`post.menu.delete`),
|
||||
callback: () => {
|
||||
this.openModal('delete')
|
||||
},
|
||||
icon: 'trash',
|
||||
})
|
||||
icon: 'trash',
|
||||
})
|
||||
}
|
||||
|
||||
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') {
|
||||
@ -155,6 +177,9 @@ export default {
|
||||
isModerator() {
|
||||
return this.$store.getters['auth/isModerator']
|
||||
},
|
||||
isAdmin() {
|
||||
return this.$store.getters['auth/isAdmin']
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openItem(route, toggleMenu) {
|
||||
|
||||
@ -2,7 +2,7 @@ import { config, shallowMount, mount, createLocalVue, RouterLinkStub } from '@vu
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
import PostCard from '.'
|
||||
import PostCard from './PostCard.vue'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import { withA11y } from '@storybook/addon-a11y'
|
||||
import HcPostCard from '~/components/PostCard'
|
||||
import HcPostCard from './PostCard.vue'
|
||||
import helpers from '~/storybook/helpers'
|
||||
|
||||
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>
|
||||
<ds-card
|
||||
: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 -->
|
||||
<nuxt-link
|
||||
@ -16,7 +16,8 @@
|
||||
<client-only>
|
||||
<hc-user :user="post.author" :trunc="35" :date-time="post.createdAt" />
|
||||
</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>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Post Title -->
|
||||
@ -61,6 +62,8 @@
|
||||
:resource="post"
|
||||
:modalsData="menuModalsData"
|
||||
:is-owner="isAuthor"
|
||||
@pinPost="pinPost"
|
||||
@unpinPost="unpinPost"
|
||||
/>
|
||||
</div>
|
||||
</client-only>
|
||||
@ -114,6 +117,9 @@ export default {
|
||||
this.deletePostCallback,
|
||||
)
|
||||
},
|
||||
isPinned() {
|
||||
return this.post && this.post.pinnedBy
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deletePostCallback() {
|
||||
@ -127,6 +133,12 @@ export default {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
pinPost(post) {
|
||||
this.$emit('pinPost', post)
|
||||
},
|
||||
unpinPost(post) {
|
||||
this.$emit('unpinPost', post)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -167,4 +179,8 @@ export default {
|
||||
text-indent: -999999px;
|
||||
}
|
||||
}
|
||||
|
||||
.post--pinned {
|
||||
border: 1px solid $color-warning;
|
||||
}
|
||||
</style>
|
||||
@ -46,4 +46,12 @@ export default {
|
||||
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>
|
||||
|
||||
@ -93,7 +93,7 @@ export default {
|
||||
return data.notifications
|
||||
},
|
||||
error(error) {
|
||||
this.$toast.error(error)
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -57,6 +57,12 @@ export const postFragment = lang => gql`
|
||||
name
|
||||
icon
|
||||
}
|
||||
pinnedBy {
|
||||
id
|
||||
name
|
||||
role
|
||||
}
|
||||
pinnedAt
|
||||
}
|
||||
`
|
||||
export const commentFragment = lang => gql`
|
||||
|
||||
@ -50,6 +50,11 @@ export default () => {
|
||||
content
|
||||
contentExcerpt
|
||||
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 = () => {
|
||||
return gql`
|
||||
query PostsEmotionsByCurrentUser($postId: ID!) {
|
||||
|
||||
@ -354,6 +354,7 @@
|
||||
},
|
||||
"post": {
|
||||
"name": "Beitrag",
|
||||
"pinned": "Meldung",
|
||||
"moreInfo": {
|
||||
"name": "Mehr Info",
|
||||
"title": "Mehr Informationen",
|
||||
|
||||
@ -355,6 +355,7 @@
|
||||
},
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"pinned": "Announcement",
|
||||
"moreInfo": {
|
||||
"name": "More info",
|
||||
"title": "More information",
|
||||
@ -368,7 +369,11 @@
|
||||
},
|
||||
"menu": {
|
||||
"edit": "Edit Post",
|
||||
"delete": "Delete Post"
|
||||
"delete": "Delete Post",
|
||||
"pin": "Pin post",
|
||||
"pinnedSuccessfully": "Post pinned successfully!",
|
||||
"unpin": "Unpin post",
|
||||
"unpinnedSuccessfully": "Post unpinned successfully!"
|
||||
},
|
||||
"comment": {
|
||||
"submit": "Comment",
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
:post="post"
|
||||
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
|
||||
@removePostFromList="deletePost"
|
||||
@pinPost="pinPost"
|
||||
@unpinPost="unpinPost"
|
||||
/>
|
||||
</masonry-grid-item>
|
||||
</template>
|
||||
@ -58,12 +60,13 @@
|
||||
<script>
|
||||
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||
import HcEmpty from '~/components/Empty'
|
||||
import HcPostCard from '~/components/PostCard'
|
||||
import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||
import HcLoadMore from '~/components/LoadMore.vue'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { filterPosts } from '~/graphql/PostQuery.js'
|
||||
import PostMutations from '~/graphql/PostMutations'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -166,6 +169,37 @@ export default {
|
||||
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: {
|
||||
Post: {
|
||||
@ -176,7 +210,7 @@ export default {
|
||||
return {
|
||||
filter: this.finalFilters,
|
||||
first: this.pageSize,
|
||||
orderBy: this.sorting,
|
||||
orderBy: ['pinnedAt_asc', this.sorting],
|
||||
offset: 0,
|
||||
}
|
||||
},
|
||||
|
||||
@ -31,10 +31,10 @@ describe('PostSlug', () => {
|
||||
$filters: {
|
||||
truncate: a => a,
|
||||
},
|
||||
// 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
|
||||
$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
|
||||
$router: {
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
:resource="post"
|
||||
:modalsData="menuModalsData"
|
||||
:is-owner="isAuthor(post.author ? post.author.id : null)"
|
||||
@pinPost="pinPost"
|
||||
@unpinPost="unpinPost"
|
||||
/>
|
||||
</client-only>
|
||||
<ds-space margin-bottom="small" />
|
||||
@ -88,6 +90,7 @@ import HcCommentList from '~/components/CommentList/CommentList'
|
||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||
import PostQuery from '~/graphql/PostQuery'
|
||||
import HcEmotions from '~/components/Emotions/Emotions'
|
||||
import PostMutations from '~/graphql/PostMutations'
|
||||
|
||||
export default {
|
||||
name: 'PostSlug',
|
||||
@ -156,6 +159,28 @@ export default {
|
||||
async createComment(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))
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
Post: {
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
<script>
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import HcPostCard from '~/components/PostCard'
|
||||
import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||
import HcCategory from '~/components/Category'
|
||||
import HcHashtag from '~/components/Hashtag/Hashtag'
|
||||
import { relatedContributions } from '~/graphql/PostQuery'
|
||||
|
||||
@ -234,6 +234,8 @@
|
||||
:post="post"
|
||||
:width="{ base: '100%', md: '100%', xl: '50%' }"
|
||||
@removePostFromList="removePostFromList"
|
||||
@pinPost="pinPost"
|
||||
@unpinPost="unpinPost"
|
||||
/>
|
||||
</masonry-grid-item>
|
||||
</template>
|
||||
@ -268,7 +270,7 @@
|
||||
<script>
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
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 HcCountTo from '~/components/CountTo.vue'
|
||||
import HcBadges from '~/components/Badges.vue'
|
||||
@ -279,9 +281,10 @@ import HcUpload from '~/components/Upload'
|
||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||
import { filterPosts } from '~/graphql/PostQuery'
|
||||
import { profilePagePosts } from '~/graphql/PostQuery'
|
||||
import UserQuery from '~/graphql/User'
|
||||
import { Block, Unblock } from '~/graphql/settings/BlockedUsers'
|
||||
import PostMutations from '~/graphql/PostMutations'
|
||||
|
||||
const tabToFilterMapping = ({ tab, id }) => {
|
||||
return {
|
||||
@ -412,6 +415,32 @@ export default {
|
||||
this.resetPostList()
|
||||
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 }) {
|
||||
/*
|
||||
* Note: followedByCountStartValue is updated to avoid counting from 0 when follow/unfollow
|
||||
@ -437,18 +466,18 @@ export default {
|
||||
apollo: {
|
||||
Post: {
|
||||
query() {
|
||||
return filterPosts(this.$i18n)
|
||||
return profilePagePosts(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
filter: this.filter,
|
||||
first: this.pageSize,
|
||||
offset: 0,
|
||||
orderBy: 'createdAt_desc',
|
||||
orderBy: ['pinnedAt_asc', 'createdAt_desc'],
|
||||
}
|
||||
},
|
||||
update({ Post }) {
|
||||
this.posts = Post
|
||||
update({ profilePagePosts }) {
|
||||
this.posts = profilePagePosts
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user