mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #2714 from Human-Connection/1724-block-users
feat: Blocked users cannot comment on posts
This commit is contained in:
commit
b47f46157c
@ -50,7 +50,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
|||||||
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User)
|
MATCH (user: User)
|
||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
AND NOT (user)-[:BLOCKED]-(author)
|
||||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||||
`
|
`
|
||||||
break
|
break
|
||||||
@ -60,8 +60,8 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
|||||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User)
|
MATCH (user: User)
|
||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)<-[:BLOCKED]-(author)
|
AND NOT (user)-[:BLOCKED]-(author)
|
||||||
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
AND NOT (user)-[:BLOCKED]-(postAuthor)
|
||||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||||
`
|
`
|
||||||
break
|
break
|
||||||
|
|||||||
@ -102,6 +102,7 @@ export default shield(
|
|||||||
PostsEmotionsCountByEmotion: allow,
|
PostsEmotionsCountByEmotion: allow,
|
||||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||||
mutedUsers: isAuthenticated,
|
mutedUsers: isAuthenticated,
|
||||||
|
blockedUsers: isAuthenticated,
|
||||||
notifications: isAuthenticated,
|
notifications: isAuthenticated,
|
||||||
Donations: isAuthenticated,
|
Donations: isAuthenticated,
|
||||||
},
|
},
|
||||||
@ -139,6 +140,8 @@ export default shield(
|
|||||||
RemovePostEmotions: isAuthenticated,
|
RemovePostEmotions: isAuthenticated,
|
||||||
muteUser: isAuthenticated,
|
muteUser: isAuthenticated,
|
||||||
unmuteUser: isAuthenticated,
|
unmuteUser: isAuthenticated,
|
||||||
|
blockUser: isAuthenticated,
|
||||||
|
unblockUser: isAuthenticated,
|
||||||
markAsRead: isAuthenticated,
|
markAsRead: isAuthenticated,
|
||||||
AddEmailAddress: isAuthenticated,
|
AddEmailAddress: isAuthenticated,
|
||||||
VerifyEmailAddress: isAuthenticated,
|
VerifyEmailAddress: isAuthenticated,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export default {
|
|||||||
AND NOT (
|
AND NOT (
|
||||||
author.deleted = true OR author.disabled = true
|
author.deleted = true OR author.disabled = true
|
||||||
OR resource.deleted = true OR resource.disabled = true
|
OR resource.deleted = true OR resource.disabled = true
|
||||||
OR (:User { id: $thisUserId })-[:BLOCKED]-(author)
|
OR (:User {id: $thisUserId})-[:MUTED]->(author)
|
||||||
)
|
)
|
||||||
WITH resource, author,
|
WITH resource, author,
|
||||||
[(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments,
|
[(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments,
|
||||||
@ -40,8 +40,7 @@ export default {
|
|||||||
YIELD node as resource, score
|
YIELD node as resource, score
|
||||||
MATCH (resource)
|
MATCH (resource)
|
||||||
WHERE score >= 0.5
|
WHERE score >= 0.5
|
||||||
AND NOT (resource.deleted = true OR resource.disabled = true
|
AND NOT (resource.deleted = true OR resource.disabled = true)
|
||||||
OR (:User { id: $thisUserId })-[:BLOCKED]-(resource))
|
|
||||||
RETURN resource {.*, __typename: labels(resource)[0]}
|
RETURN resource {.*, __typename: labels(resource)[0]}
|
||||||
LIMIT $limit
|
LIMIT $limit
|
||||||
`
|
`
|
||||||
|
|||||||
@ -23,6 +23,21 @@ export const getMutedUsers = async context => {
|
|||||||
return mutedUsers
|
return mutedUsers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getBlockedUsers = async context => {
|
||||||
|
const { neode } = context
|
||||||
|
const userModel = neode.model('User')
|
||||||
|
let blockedUsers = neode
|
||||||
|
.query()
|
||||||
|
.match('user', userModel)
|
||||||
|
.where('user.id', context.user.id)
|
||||||
|
.relationship(userModel.relationships().get('blocked'))
|
||||||
|
.to('blocked', userModel)
|
||||||
|
.return('blocked')
|
||||||
|
blockedUsers = await blockedUsers.execute()
|
||||||
|
blockedUsers = blockedUsers.records.map(r => r.get('blocked').properties)
|
||||||
|
return blockedUsers
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
mutedUsers: async (object, args, context, resolveInfo) => {
|
mutedUsers: async (object, args, context, resolveInfo) => {
|
||||||
@ -32,6 +47,13 @@ export default {
|
|||||||
throw new UserInputError(e.message)
|
throw new UserInputError(e.message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
blockedUsers: async (object, args, context, resolveInfo) => {
|
||||||
|
try {
|
||||||
|
return getBlockedUsers(context)
|
||||||
|
} catch (e) {
|
||||||
|
throw new UserInputError(e.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
User: async (object, args, context, resolveInfo) => {
|
User: async (object, args, context, resolveInfo) => {
|
||||||
const { email } = args
|
const { email } = args
|
||||||
if (email) {
|
if (email) {
|
||||||
@ -86,7 +108,7 @@ export default {
|
|||||||
const unmutedUser = await neode.find('User', params.id)
|
const unmutedUser = await neode.find('User', params.id)
|
||||||
return unmutedUser.toJson()
|
return unmutedUser.toJson()
|
||||||
},
|
},
|
||||||
block: async (object, args, context, resolveInfo) => {
|
blockUser: async (object, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
if (currentUser.id === args.id) return null
|
if (currentUser.id === args.id) return null
|
||||||
await neode.cypher(
|
await neode.cypher(
|
||||||
@ -103,7 +125,7 @@ export default {
|
|||||||
await user.relateTo(blockedUser, 'blocked')
|
await user.relateTo(blockedUser, 'blocked')
|
||||||
return blockedUser.toJson()
|
return blockedUser.toJson()
|
||||||
},
|
},
|
||||||
unblock: async (object, args, context, resolveInfo) => {
|
unblockUser: async (object, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
if (currentUser.id === args.id) return null
|
if (currentUser.id === args.id) return null
|
||||||
await neode.cypher(
|
await neode.cypher(
|
||||||
@ -229,7 +251,7 @@ export default {
|
|||||||
boolean: {
|
boolean: {
|
||||||
followedByCurrentUser:
|
followedByCurrentUser:
|
||||||
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||||
isBlocked:
|
blocked:
|
||||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||||
isMuted:
|
isMuted:
|
||||||
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||||
|
|||||||
@ -68,10 +68,11 @@ type User {
|
|||||||
RETURN COUNT(u) >= 1
|
RETURN COUNT(u) >= 1
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
isBlocked: Boolean! @cypher(
|
|
||||||
|
blocked: Boolean! @cypher(
|
||||||
statement: """
|
statement: """
|
||||||
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
|
MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
|
||||||
RETURN COUNT(u) >= 1
|
RETURN COUNT(user) >= 1
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -207,6 +208,6 @@ type Mutation {
|
|||||||
|
|
||||||
muteUser(id: ID!): User
|
muteUser(id: ID!): User
|
||||||
unmuteUser(id: ID!): User
|
unmuteUser(id: ID!): User
|
||||||
block(id: ID!): User
|
blockUser(id: ID!): User
|
||||||
unblock(id: ID!): User
|
unblockUser(id: ID!): User
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,15 +11,24 @@ Then("I should have one item in the select dropdown", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("the search has no results", () => {
|
Then("the search should not contain posts by the annoying user", () => {
|
||||||
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
||||||
expect($li).to.have.length(1);
|
expect($li).to.have.length(1);
|
||||||
|
})
|
||||||
|
cy.get(".ds-select-dropdown")
|
||||||
|
.should("not.have.class", '.search-post')
|
||||||
|
.should("not.contain", 'Spam')
|
||||||
});
|
});
|
||||||
cy.get(".ds-select-dropdown").should("contain", 'Nothing found');
|
|
||||||
|
Then("the search should contain the annoying user", () => {
|
||||||
|
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
||||||
|
expect($li).to.have.length(1);
|
||||||
|
})
|
||||||
|
cy.get(".ds-select-dropdown .user-teaser .slug").should("contain", '@spammy-spammer');
|
||||||
cy.get(".searchable-input .ds-select-search")
|
cy.get(".searchable-input .ds-select-search")
|
||||||
.focus()
|
.focus()
|
||||||
.type("{esc}");
|
.type("{esc}");
|
||||||
});
|
})
|
||||||
|
|
||||||
Then("I should see the following posts in the select dropdown:", table => {
|
Then("I should see the following posts in the select dropdown:", table => {
|
||||||
table.hashes().forEach(({ title }) => {
|
table.hashes().forEach(({ title }) => {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ const narratorParams = {
|
|||||||
|
|
||||||
const annoyingParams = {
|
const annoyingParams = {
|
||||||
email: "spammy-spammer@example.org",
|
email: "spammy-spammer@example.org",
|
||||||
|
slug: 'spammy-spammer',
|
||||||
password: "1234",
|
password: "1234",
|
||||||
...termsAndConditionsAgreedVersion
|
...termsAndConditionsAgreedVersion
|
||||||
};
|
};
|
||||||
@ -39,8 +40,12 @@ Given("I am logged in", () => {
|
|||||||
cy.login(loginCredentials);
|
cy.login(loginCredentials);
|
||||||
});
|
});
|
||||||
|
|
||||||
Given("I am logged in as the muted user", () => {
|
Given("the {string} user searches for {string}", (_, postTitle) => {
|
||||||
cy.login({ email: annoyingParams.email, password: '1234' });
|
cy.logout()
|
||||||
|
.login({ email: annoyingParams.email, password: '1234' })
|
||||||
|
.get(".searchable-input .ds-select-search")
|
||||||
|
.focus()
|
||||||
|
.type(postTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
Given("we have a selection of categories", () => {
|
Given("we have a selection of categories", () => {
|
||||||
@ -123,6 +128,12 @@ When("I visit the {string} page", page => {
|
|||||||
cy.openPage(page);
|
cy.openPage(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
When("a blocked user visits the post page of one of my authored posts", () => {
|
||||||
|
cy.logout()
|
||||||
|
.login({ email: annoyingParams.email, password: annoyingParams.password })
|
||||||
|
.openPage('/post/previously-created-post')
|
||||||
|
})
|
||||||
|
|
||||||
Given("I am on the {string} page", page => {
|
Given("I am on the {string} page", page => {
|
||||||
cy.openPage(page);
|
cy.openPage(page);
|
||||||
});
|
});
|
||||||
@ -486,7 +497,7 @@ Given("I follow the user {string}", name => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Given('"Spammy Spammer" wrote a post {string}', title => {
|
Given('{string} wrote a post {string}', (_, title) => {
|
||||||
cy.createCategories("cat21")
|
cy.createCategories("cat21")
|
||||||
.factory()
|
.factory()
|
||||||
.create("Post", {
|
.create("Post", {
|
||||||
@ -501,7 +512,7 @@ Then("the list of posts of this user is empty", () => {
|
|||||||
cy.get(".main-container").find(".ds-space.hc-empty");
|
cy.get(".main-container").find(".ds-space.hc-empty");
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("nobody is following the user profile anymore", () => {
|
Then("I get removed from his follower collection", () => {
|
||||||
cy.get(".ds-card-content").not(".post-link");
|
cy.get(".ds-card-content").not(".post-link");
|
||||||
cy.get(".main-container").contains(
|
cy.get(".main-container").contains(
|
||||||
".ds-card-content",
|
".ds-card-content",
|
||||||
@ -533,6 +544,20 @@ When("I mute the user {string}", name => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
When("I block the user {string}", name => {
|
||||||
|
cy.neode()
|
||||||
|
.first("User", {
|
||||||
|
name
|
||||||
|
})
|
||||||
|
.then(blockedUser => {
|
||||||
|
cy.neode()
|
||||||
|
.first("User", {
|
||||||
|
name: narratorParams.name
|
||||||
|
})
|
||||||
|
.relateTo(blockedUser, "blocked");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
When("I log in with:", table => {
|
When("I log in with:", table => {
|
||||||
const [firstRow] = table.hashes();
|
const [firstRow] = table.hashes();
|
||||||
const {
|
const {
|
||||||
@ -551,3 +576,11 @@ Then("I see only one post with the title {string}", title => {
|
|||||||
.should("have.length", 1);
|
.should("have.length", 1);
|
||||||
cy.get(".main-container").contains(".post-link", title);
|
cy.get(".main-container").contains(".post-link", title);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Then("they should not see the comment from", () => {
|
||||||
|
cy.get(".ds-card-footer").children().should('not.have.class', 'comment-form')
|
||||||
|
})
|
||||||
|
|
||||||
|
Then("they should see a text explaining commenting is not possible", () => {
|
||||||
|
cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.")
|
||||||
|
})
|
||||||
46
cypress/integration/user_profile/BlockUser.feature
Normal file
46
cypress/integration/user_profile/BlockUser.feature
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
Feature: Block a User
|
||||||
|
As a user
|
||||||
|
I'd like to have a button to block another user
|
||||||
|
To prevent him from seeing and interacting with my contributions
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given I have a user account
|
||||||
|
And there is an annoying user called "Harassing User"
|
||||||
|
And I am logged in
|
||||||
|
|
||||||
|
Scenario: Block a user
|
||||||
|
Given I am on the profile page of the annoying user
|
||||||
|
When I click on "Block user" from the content menu in the user info box
|
||||||
|
And I navigate to my "Blocked users" settings page
|
||||||
|
Then I can see the following table:
|
||||||
|
| Avatar | Name |
|
||||||
|
| | Harassing User |
|
||||||
|
|
||||||
|
Scenario: Blocked user cannot interact with my contributions
|
||||||
|
Given I block the user "Harassing User"
|
||||||
|
And I previously created a post
|
||||||
|
And a blocked user visits the post page of one of my authored posts
|
||||||
|
Then they should not see the comment from
|
||||||
|
And they should see a text explaining commenting is not possible
|
||||||
|
|
||||||
|
Scenario: Block a previously followed user
|
||||||
|
Given I follow the user "Harassing User"
|
||||||
|
When I visit the profile page of the annoying user
|
||||||
|
And I click on "Block user" from the content menu in the user info box
|
||||||
|
And I get removed from his follower collection
|
||||||
|
|
||||||
|
Scenario: Posts of blocked users are not filtered from search results
|
||||||
|
Given "Harassing User" wrote a post "You can still see my posts"
|
||||||
|
And I block the user "Harassing User"
|
||||||
|
When I search for "see"
|
||||||
|
Then I should see the following posts in the select dropdown:
|
||||||
|
| title |
|
||||||
|
| You can still see my posts |
|
||||||
|
|
||||||
|
Scenario: Blocked users can still see my posts
|
||||||
|
Given I previously created a post
|
||||||
|
And I block the user "Harassing User"
|
||||||
|
And the "blocked" user searches for "previously created"
|
||||||
|
Then I should see the following posts in the select dropdown:
|
||||||
|
| title |
|
||||||
|
| previously created post |
|
||||||
@ -1,8 +1,7 @@
|
|||||||
Feature: Mute a User
|
Feature: Mute a User
|
||||||
As a user
|
As a user
|
||||||
I'd like to have a button to mute another user
|
I'd like to have a button to mute another user
|
||||||
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
To prevent him from seeing and interacting with my contributions
|
||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given I have a user account
|
Given I have a user account
|
||||||
And there is an annoying user called "Spammy Spammer"
|
And there is an annoying user called "Spammy Spammer"
|
||||||
@ -22,9 +21,9 @@ Feature: Mute a User
|
|||||||
When I visit the profile page of the annoying user
|
When I visit the profile page of the annoying user
|
||||||
And I click on "Mute user" from the content menu in the user info box
|
And I click on "Mute user" from the content menu in the user info box
|
||||||
Then the list of posts of this user is empty
|
Then the list of posts of this user is empty
|
||||||
And nobody is following the user profile anymore
|
And I get removed from his follower collection
|
||||||
|
|
||||||
Scenario: Posts of muted users are filtered from search results
|
Scenario: Posts of muted users are filtered from search results, users are not
|
||||||
Given we have the following posts in our database:
|
Given we have the following posts in our database:
|
||||||
| id | title | content |
|
| id | title | content |
|
||||||
| im-not-muted | Post that should be seen | cause I'm not muted |
|
| im-not-muted | Post that should be seen | cause I'm not muted |
|
||||||
@ -36,7 +35,8 @@ Feature: Mute a User
|
|||||||
When I mute the user "Spammy Spammer"
|
When I mute the user "Spammy Spammer"
|
||||||
And I refresh the page
|
And I refresh the page
|
||||||
And I search for "Spam"
|
And I search for "Spam"
|
||||||
Then the search has no results
|
Then the search should not contain posts by the annoying user
|
||||||
|
But the search should contain the annoying user
|
||||||
But I search for "not muted"
|
But I search for "not muted"
|
||||||
Then I should see the following posts in the select dropdown:
|
Then I should see the following posts in the select dropdown:
|
||||||
| title |
|
| title |
|
||||||
@ -45,9 +45,7 @@ Feature: Mute a User
|
|||||||
Scenario: Muted users can still see my posts
|
Scenario: Muted users can still see my posts
|
||||||
Given I previously created a post
|
Given I previously created a post
|
||||||
And I mute the user "Spammy Spammer"
|
And I mute the user "Spammy Spammer"
|
||||||
Given I log out
|
And the "muted" user searches for "previously created"
|
||||||
And I am logged in as the muted user
|
|
||||||
When I search for "previously created"
|
|
||||||
Then I should see the following posts in the select dropdown:
|
Then I should see the following posts in the select dropdown:
|
||||||
| title |
|
| title |
|
||||||
| previously created post |
|
| previously created post |
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
</client-only>
|
</client-only>
|
||||||
</ds-space>
|
</ds-space>
|
||||||
<div v-if="openEditCommentMenu">
|
<div v-if="openEditCommentMenu">
|
||||||
<hc-comment-form
|
<comment-form
|
||||||
:update="true"
|
:update="true"
|
||||||
:post="post"
|
:post="post"
|
||||||
:comment="comment"
|
:comment="comment"
|
||||||
@ -64,7 +64,7 @@ import { COMMENT_MAX_UNTRUNCATED_LENGTH, COMMENT_TRUNCATE_TO_LENGTH } from '~/co
|
|||||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||||
import HcCommentForm from '~/components/CommentForm/CommentForm'
|
import CommentForm from '~/components/CommentForm/CommentForm'
|
||||||
import CommentMutations from '~/graphql/CommentMutations'
|
import CommentMutations from '~/graphql/CommentMutations'
|
||||||
import scrollToAnchor from '~/mixins/scrollToAnchor.js'
|
import scrollToAnchor from '~/mixins/scrollToAnchor.js'
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ export default {
|
|||||||
UserTeaser,
|
UserTeaser,
|
||||||
ContentMenu,
|
ContentMenu,
|
||||||
ContentViewer,
|
ContentViewer,
|
||||||
HcCommentForm,
|
CommentForm,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
routeHash: { type: String, default: () => '' },
|
routeHash: { type: String, default: () => '' },
|
||||||
|
|||||||
@ -161,7 +161,7 @@ export default {
|
|||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('unmute', this.resource)
|
this.$emit('unmute', this.resource)
|
||||||
},
|
},
|
||||||
icon: 'user-plus',
|
icon: 'eye',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
routes.push({
|
routes.push({
|
||||||
@ -169,6 +169,23 @@ export default {
|
|||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('mute', this.resource)
|
this.$emit('mute', this.resource)
|
||||||
},
|
},
|
||||||
|
icon: 'eye-slash',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.resource.blocked) {
|
||||||
|
routes.push({
|
||||||
|
label: this.$t(`settings.blocked-users.unblock`),
|
||||||
|
callback: () => {
|
||||||
|
this.$emit('unblock', this.resource)
|
||||||
|
},
|
||||||
|
icon: 'user-plus',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
routes.push({
|
||||||
|
label: this.$t(`settings.blocked-users.block`),
|
||||||
|
callback: () => {
|
||||||
|
this.$emit('block', this.resource)
|
||||||
|
},
|
||||||
icon: 'user-times',
|
icon: 'user-times',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
emptyText() {
|
emptyText() {
|
||||||
return this.isActive && !this.pending ? this.$t('search.failed') : this.$t('search.hint')
|
return this.isActive && !this.loading ? this.$t('search.failed') : this.$t('search.hint')
|
||||||
},
|
},
|
||||||
isActive() {
|
isActive() {
|
||||||
return !isEmpty(this.previousSearchTerm)
|
return !isEmpty(this.previousSearchTerm)
|
||||||
@ -104,7 +104,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
onEnter(event) {
|
onEnter(event) {
|
||||||
clearTimeout(this.searchProcess)
|
clearTimeout(this.searchProcess)
|
||||||
if (!this.pending) {
|
if (!this.loading) {
|
||||||
this.previousSearchTerm = this.unprocessedSearchInput
|
this.previousSearchTerm = this.unprocessedSearchInput
|
||||||
this.$emit('query', this.unprocessedSearchInput)
|
this.$emit('query', this.unprocessedSearchInput)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export default i18n => {
|
|||||||
...user
|
...user
|
||||||
...userCounts
|
...userCounts
|
||||||
...locationAndBadges
|
...locationAndBadges
|
||||||
|
blocked
|
||||||
}
|
}
|
||||||
comments(orderBy: createdAt_asc) {
|
comments(orderBy: createdAt_asc) {
|
||||||
...comment
|
...comment
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export default i18n => {
|
|||||||
createdAt
|
createdAt
|
||||||
followedByCurrentUser
|
followedByCurrentUser
|
||||||
isMuted
|
isMuted
|
||||||
|
blocked
|
||||||
following(first: 7) {
|
following(first: 7) {
|
||||||
...user
|
...user
|
||||||
...userCounts
|
...userCounts
|
||||||
|
|||||||
43
webapp/graphql/settings/BlockedUsers.js
Normal file
43
webapp/graphql/settings/BlockedUsers.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const blockedUsers = () => {
|
||||||
|
return gql`
|
||||||
|
{
|
||||||
|
blockedUsers {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
avatar
|
||||||
|
about
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blockUser = () => {
|
||||||
|
return gql`
|
||||||
|
mutation($id: ID!) {
|
||||||
|
blockUser(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
blocked
|
||||||
|
followedByCurrentUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unblockUser = () => {
|
||||||
|
return gql`
|
||||||
|
mutation($id: ID!) {
|
||||||
|
unblockUser(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
blocked
|
||||||
|
followedByCurrentUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
@ -70,6 +70,11 @@
|
|||||||
"passwordStrength4": "Sehr sicheres Passwort"
|
"passwordStrength4": "Sehr sicheres Passwort"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"privacy": {
|
||||||
|
"name": "Privatsphäre",
|
||||||
|
"make-shouts-public": "Teile von mir empfohlene Artikel öffentlich auf meinem Profil",
|
||||||
|
"success-update": "Privatsphäre-Einstellungen gespeichert"
|
||||||
|
},
|
||||||
"invites": {
|
"invites": {
|
||||||
"name": "Einladungen"
|
"name": "Einladungen"
|
||||||
},
|
},
|
||||||
@ -160,10 +165,27 @@
|
|||||||
"unmute": "Stummschaltung aufheben",
|
"unmute": "Stummschaltung aufheben",
|
||||||
"unmuted": "{name} ist nicht mehr stummgeschaltet"
|
"unmuted": "{name} ist nicht mehr stummgeschaltet"
|
||||||
},
|
},
|
||||||
"privacy": {
|
"blocked-users": {
|
||||||
"name": "Privatsphäre",
|
"name": "Blocked users",
|
||||||
"make-shouts-public": "Teile von mir empfohlene Artikel öffentlich auf meinem Profil",
|
"explanation": {
|
||||||
"success-update": "Privatsphäre-Einstellungen gespeichert"
|
"intro": "Wenn ein anderer Benutzer von dir blockiert wurde, dann passiert folgendes:",
|
||||||
|
"your-perspective": "Du kannst keine Beiträge der blockierten Person mehr kommentieren.",
|
||||||
|
"their-perspective": "Die blockierte Person kann deine Beiträge nicht mehr kommentieren",
|
||||||
|
"notifications": "Von dir blockierte Personen erhalten keine Benachrichtigungen mehr, wenn sie in deinen Beiträgen erwähnt werden.",
|
||||||
|
"closing": "Das sollte fürs Erste genügen, damit blockierte Benutzer dich nicht mehr länger belästigen können.",
|
||||||
|
"commenting-disabled": "Du kannst den Beitrag derzeit nicht kommentieren.",
|
||||||
|
"commenting-explanation": "Dafür kann es mehrere Gründe geben, bitte schau in unsere "
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"name": "Name",
|
||||||
|
"slug": "Alias",
|
||||||
|
"unblock": "Entsperren"
|
||||||
|
},
|
||||||
|
"empty": "Bislang hast du niemanden blockiert.",
|
||||||
|
"how-to": "Du kannst andere Benutzer auf deren Profilseite über das Inhaltsmenü blockieren.",
|
||||||
|
"block": "Nutzer blockieren",
|
||||||
|
"unblock": "Nutzer entsperren",
|
||||||
|
"unblocked": "{name} ist wieder entsperrt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@ -329,6 +329,28 @@
|
|||||||
"mute": "Mute user",
|
"mute": "Mute user",
|
||||||
"unmute": "Unmute user",
|
"unmute": "Unmute user",
|
||||||
"unmuted": "{name} is unmuted again"
|
"unmuted": "{name} is unmuted again"
|
||||||
|
},
|
||||||
|
"blocked-users": {
|
||||||
|
"name": "Blocked users",
|
||||||
|
"explanation": {
|
||||||
|
"intro": "If another user has been blocked by you, this is what happens:",
|
||||||
|
"your-perspective": "You will no longer be able to interact with their contributions.",
|
||||||
|
"their-perspective": "Vice versa: The blocked person will also no longer be able to interact with your contributions.",
|
||||||
|
"notifications": "Blocked users will no longer receive notifications if they mention each other.",
|
||||||
|
"closing": "This should be sufficient for now so that blocked users can no longer bother you.",
|
||||||
|
"commenting-disabled": "Commenting is not possible at this time on this post.",
|
||||||
|
"commenting-explanation": "This can happen for several reasons, please see our "
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"name": "Name",
|
||||||
|
"slug": "Slug",
|
||||||
|
"unblock": "Unblock"
|
||||||
|
},
|
||||||
|
"empty": "So far, you have not blocked anybody.",
|
||||||
|
"how-to": "You can block other users on their profile page via the content menu.",
|
||||||
|
"block": "Block user",
|
||||||
|
"unblock": "Unblock user",
|
||||||
|
"unblocked": "{name} is unblocked again"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@ -90,7 +90,17 @@
|
|||||||
@toggleNewCommentForm="toggleNewCommentForm"
|
@toggleNewCommentForm="toggleNewCommentForm"
|
||||||
/>
|
/>
|
||||||
<ds-space margin-bottom="large" />
|
<ds-space margin-bottom="large" />
|
||||||
<hc-comment-form v-if="showNewCommentForm" :post="post" @createComment="createComment" />
|
<comment-form
|
||||||
|
v-if="showNewCommentForm && !post.author.blocked"
|
||||||
|
:post="post"
|
||||||
|
@createComment="createComment"
|
||||||
|
/>
|
||||||
|
<ds-placeholder v-else>
|
||||||
|
{{ $t('settings.blocked-users.explanation.commenting-disabled') }}
|
||||||
|
<br />
|
||||||
|
{{ $t('settings.blocked-users.explanation.commenting-explanation') }}
|
||||||
|
<a href="https://support.human-connection.org/kb/" target="_blank">FAQ</a>
|
||||||
|
</ds-placeholder>
|
||||||
</ds-section>
|
</ds-section>
|
||||||
</ds-card>
|
</ds-card>
|
||||||
</transition>
|
</transition>
|
||||||
@ -103,7 +113,7 @@ import HcHashtag from '~/components/Hashtag/Hashtag'
|
|||||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
import HcShoutButton from '~/components/ShoutButton.vue'
|
import HcShoutButton from '~/components/ShoutButton.vue'
|
||||||
import HcCommentForm from '~/components/CommentForm/CommentForm'
|
import CommentForm from '~/components/CommentForm/CommentForm'
|
||||||
import HcCommentList from '~/components/CommentList/CommentList'
|
import HcCommentList from '~/components/CommentList/CommentList'
|
||||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||||
import PostQuery from '~/graphql/PostQuery'
|
import PostQuery from '~/graphql/PostQuery'
|
||||||
@ -122,7 +132,7 @@ export default {
|
|||||||
UserTeaser,
|
UserTeaser,
|
||||||
HcShoutButton,
|
HcShoutButton,
|
||||||
ContentMenu,
|
ContentMenu,
|
||||||
HcCommentForm,
|
CommentForm,
|
||||||
HcCommentList,
|
HcCommentList,
|
||||||
HcEmotions,
|
HcEmotions,
|
||||||
ContentViewer,
|
ContentViewer,
|
||||||
@ -139,15 +149,10 @@ export default {
|
|||||||
title: 'loading',
|
title: 'loading',
|
||||||
showNewCommentForm: true,
|
showNewCommentForm: true,
|
||||||
blurred: false,
|
blurred: false,
|
||||||
|
blocked: null,
|
||||||
|
postAuthor: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
Post(post) {
|
|
||||||
this.post = post[0] || {}
|
|
||||||
this.title = this.post.title
|
|
||||||
this.blurred = this.post.imageBlurred
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// NOTE: quick fix for jumping flexbox implementation
|
// NOTE: quick fix for jumping flexbox implementation
|
||||||
@ -216,6 +221,12 @@ export default {
|
|||||||
id: this.$route.params.id,
|
id: this.$route.params.id,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
update({ Post }) {
|
||||||
|
this.post = Post[0] || {}
|
||||||
|
this.title = this.post.title
|
||||||
|
this.blurred = this.post.imageBlurred
|
||||||
|
this.postAuthor = this.post.author
|
||||||
|
},
|
||||||
fetchPolicy: 'cache-and-network',
|
fetchPolicy: 'cache-and-network',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,6 +24,8 @@
|
|||||||
class="user-content-menu"
|
class="user-content-menu"
|
||||||
@mute="muteUser"
|
@mute="muteUser"
|
||||||
@unmute="unmuteUser"
|
@unmute="unmuteUser"
|
||||||
|
@block="blockUser"
|
||||||
|
@unblock="unblockUser"
|
||||||
/>
|
/>
|
||||||
</client-only>
|
</client-only>
|
||||||
<ds-space margin="small">
|
<ds-space margin="small">
|
||||||
@ -64,20 +66,21 @@
|
|||||||
</client-only>
|
</client-only>
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
</ds-flex>
|
</ds-flex>
|
||||||
<ds-space margin="small">
|
<div v-if="!myProfile" class="action-buttons">
|
||||||
<template v-if="!myProfile">
|
<base-button v-if="user.blocked" @click="unblockUser(user)">
|
||||||
|
{{ $t('settings.blocked-users.unblock') }}
|
||||||
|
</base-button>
|
||||||
|
<base-button v-if="user.isMuted" @click="unmuteUser(user)">
|
||||||
|
{{ $t('settings.muted-users.unmute') }}
|
||||||
|
</base-button>
|
||||||
<hc-follow-button
|
<hc-follow-button
|
||||||
v-if="!user.isMuted"
|
v-if="!(user.blocked || user.isMuted)"
|
||||||
:follow-id="user.id"
|
:follow-id="user.id"
|
||||||
:is-followed="user.followedByCurrentUser"
|
:is-followed="user.followedByCurrentUser"
|
||||||
@optimistic="optimisticFollow"
|
@optimistic="optimisticFollow"
|
||||||
@update="updateFollow"
|
@update="updateFollow"
|
||||||
/>
|
/>
|
||||||
<base-button v-else @click="unmuteUser(user)" class="unblock-user-button">
|
</div>
|
||||||
{{ $t('settings.muted-users.unmute') }}
|
|
||||||
</base-button>
|
|
||||||
</template>
|
|
||||||
</ds-space>
|
|
||||||
<template v-if="user.about">
|
<template v-if="user.about">
|
||||||
<hr />
|
<hr />
|
||||||
<ds-space margin-top="small" margin-bottom="small">
|
<ds-space margin-top="small" margin-bottom="small">
|
||||||
@ -285,6 +288,7 @@ import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
|||||||
import { profilePagePosts } from '~/graphql/PostQuery'
|
import { profilePagePosts } from '~/graphql/PostQuery'
|
||||||
import UserQuery from '~/graphql/User'
|
import UserQuery from '~/graphql/User'
|
||||||
import { muteUser, unmuteUser } from '~/graphql/settings/MutedUsers'
|
import { muteUser, unmuteUser } from '~/graphql/settings/MutedUsers'
|
||||||
|
import { blockUser, unblockUser } from '~/graphql/settings/BlockedUsers'
|
||||||
import PostMutations from '~/graphql/PostMutations'
|
import PostMutations from '~/graphql/PostMutations'
|
||||||
import UpdateQuery from '~/components/utils/UpdateQuery'
|
import UpdateQuery from '~/components/utils/UpdateQuery'
|
||||||
|
|
||||||
@ -417,6 +421,24 @@ export default {
|
|||||||
this.$apollo.queries.profilePagePosts.refetch()
|
this.$apollo.queries.profilePagePosts.refetch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async blockUser(user) {
|
||||||
|
try {
|
||||||
|
await this.$apollo.mutate({ mutation: blockUser(), variables: { id: user.id } })
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(error.message)
|
||||||
|
} finally {
|
||||||
|
this.$apollo.queries.User.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async unblockUser(user) {
|
||||||
|
try {
|
||||||
|
this.$apollo.mutate({ mutation: unblockUser(), variables: { id: user.id } })
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(error.message)
|
||||||
|
} finally {
|
||||||
|
this.$apollo.queries.User.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
pinPost(post) {
|
pinPost(post) {
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
@ -559,8 +581,13 @@ export default {
|
|||||||
.profile-post-add-button {
|
.profile-post-add-button {
|
||||||
box-shadow: $box-shadow-x-large;
|
box-shadow: $box-shadow-x-large;
|
||||||
}
|
}
|
||||||
.unblock-user-button {
|
.action-buttons {
|
||||||
|
margin: $space-small 0;
|
||||||
|
|
||||||
|
> .base-button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: $space-x-small;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -43,6 +43,10 @@ export default {
|
|||||||
name: this.$t('settings.muted-users.name'),
|
name: this.$t('settings.muted-users.name'),
|
||||||
path: `/settings/muted-users`,
|
path: `/settings/muted-users`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: this.$t('settings.blocked-users.name'),
|
||||||
|
path: `/settings/blocked-users`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: this.$t('settings.embeds.name'),
|
name: this.$t('settings.embeds.name'),
|
||||||
path: `/settings/embeds`,
|
path: `/settings/embeds`,
|
||||||
|
|||||||
69
webapp/pages/settings/blocked-users.spec.js
Normal file
69
webapp/pages/settings/blocked-users.spec.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||||
|
import BlockedUsers from './blocked-users.vue'
|
||||||
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
import Filters from '~/plugins/vue-filters'
|
||||||
|
import { unblockUser } from '~/graphql/settings/BlockedUsers'
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
|
localVue.use(Styleguide)
|
||||||
|
localVue.use(Filters)
|
||||||
|
|
||||||
|
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||||
|
|
||||||
|
describe('blocked-users.vue', () => {
|
||||||
|
let wrapper
|
||||||
|
let mocks
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks = {
|
||||||
|
$t: jest.fn(),
|
||||||
|
$apollo: {
|
||||||
|
mutate: jest.fn(),
|
||||||
|
queries: {
|
||||||
|
blockedUsers: {
|
||||||
|
refetch: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
error: jest.fn(),
|
||||||
|
success: jest.fn(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(BlockedUsers, { mocks, localVue })
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.is('div')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('given a list of blocked users', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const blockedUsers = [{ id: 'u1', name: 'John Doe', slug: 'john-doe', avatar: '' }]
|
||||||
|
wrapper.setData({ blockedUsers })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click unblock', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.find('.base-button').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls unblock mutation with given user', () => {
|
||||||
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
|
||||||
|
mutation: unblockUser(),
|
||||||
|
variables: { id: 'u1' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
118
webapp/pages/settings/blocked-users.vue
Normal file
118
webapp/pages/settings/blocked-users.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ds-space>
|
||||||
|
<ds-card :header="$t('settings.blocked-users.name')">
|
||||||
|
<ds-text>
|
||||||
|
{{ $t('settings.blocked-users.explanation.intro') }}
|
||||||
|
</ds-text>
|
||||||
|
<ds-list>
|
||||||
|
<ds-list-item>
|
||||||
|
{{ $t('settings.blocked-users.explanation.your-perspective') }}
|
||||||
|
</ds-list-item>
|
||||||
|
<ds-list-item>
|
||||||
|
{{ $t('settings.blocked-users.explanation.their-perspective') }}
|
||||||
|
</ds-list-item>
|
||||||
|
<ds-list-item>
|
||||||
|
{{ $t('settings.blocked-users.explanation.notifications') }}
|
||||||
|
</ds-list-item>
|
||||||
|
</ds-list>
|
||||||
|
</ds-card>
|
||||||
|
</ds-space>
|
||||||
|
<ds-card v-if="blockedUsers && blockedUsers.length">
|
||||||
|
<ds-table :data="blockedUsers" :fields="fields" condensed>
|
||||||
|
<template #avatar="scope">
|
||||||
|
<nuxt-link
|
||||||
|
:to="{
|
||||||
|
name: 'profile-id-slug',
|
||||||
|
params: { id: scope.row.id, slug: scope.row.slug },
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<user-avatar :user="scope.row" size="small" />
|
||||||
|
</nuxt-link>
|
||||||
|
</template>
|
||||||
|
<template #name="scope">
|
||||||
|
<nuxt-link
|
||||||
|
:to="{
|
||||||
|
name: 'profile-id-slug',
|
||||||
|
params: { id: scope.row.id, slug: scope.row.slug },
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<b>{{ scope.row.name | truncate(20) }}</b>
|
||||||
|
</nuxt-link>
|
||||||
|
</template>
|
||||||
|
<template #slug="scope">
|
||||||
|
<nuxt-link
|
||||||
|
:to="{
|
||||||
|
name: 'profile-id-slug',
|
||||||
|
params: { id: scope.row.id, slug: scope.row.slug },
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<b>{{ scope.row.slug | truncate(20) }}</b>
|
||||||
|
</nuxt-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #unblockUser="scope">
|
||||||
|
<base-button circle size="small" @click="unblockUser(scope)" icon="user-plus" />
|
||||||
|
</template>
|
||||||
|
</ds-table>
|
||||||
|
</ds-card>
|
||||||
|
<ds-card v-else>
|
||||||
|
<ds-space>
|
||||||
|
<ds-placeholder>
|
||||||
|
{{ $t('settings.blocked-users.empty') }}
|
||||||
|
</ds-placeholder>
|
||||||
|
</ds-space>
|
||||||
|
<ds-space>
|
||||||
|
<ds-text align="center">
|
||||||
|
{{ $t('settings.blocked-users.how-to') }}
|
||||||
|
</ds-text>
|
||||||
|
</ds-space>
|
||||||
|
</ds-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { blockedUsers, unblockUser } from '~/graphql/settings/BlockedUsers'
|
||||||
|
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
UserAvatar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
blockedUsers: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fields() {
|
||||||
|
return {
|
||||||
|
avatar: '',
|
||||||
|
name: this.$t('settings.blocked-users.columns.name'),
|
||||||
|
slug: this.$t('settings.blocked-users.columns.slug'),
|
||||||
|
unblockUser: this.$t('settings.blocked-users.columns.unblock'),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
blockedUsers: { query: blockedUsers, fetchPolicy: 'cache-and-network' },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async unblockUser(user) {
|
||||||
|
await this.$apollo.mutate({
|
||||||
|
mutation: unblockUser(),
|
||||||
|
variables: { id: user.row.id },
|
||||||
|
})
|
||||||
|
this.$apollo.queries.blockedUsers.refetch()
|
||||||
|
const { name } = user.row
|
||||||
|
this.$toast.success(this.$t('settings.blocked-user.unblocked', { name }))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ds-table-col {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -46,7 +46,7 @@ describe('muted-users.vue', () => {
|
|||||||
expect(wrapper.is('div')).toBe(true)
|
expect(wrapper.is('div')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given a list of blocked users', () => {
|
describe('given a list of muted users', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mutedUsers = [{ id: 'u1', name: 'John Doe', slug: 'john-doe', avatar: '' }]
|
const mutedUsers = [{ id: 'u1', name: 'John Doe', slug: 'john-doe', avatar: '' }]
|
||||||
wrapper.setData({ mutedUsers })
|
wrapper.setData({ mutedUsers })
|
||||||
@ -54,7 +54,7 @@ describe('muted-users.vue', () => {
|
|||||||
|
|
||||||
describe('click unmute', () => {
|
describe('click unmute', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper.find('button').trigger('click')
|
wrapper.find('.base-button').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls unmute mutation with given user', () => {
|
it('calls unmute mutation with given user', () => {
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
</ds-space>
|
</ds-space>
|
||||||
<ds-card v-if="mutedUsers && mutedUsers.length">
|
<ds-card v-if="mutedUsers && mutedUsers.length">
|
||||||
<ds-table :data="mutedUsers" :fields="fields" condensed>
|
<ds-table :data="mutedUsers" :fields="fields" condensed>
|
||||||
<template slot="avatar" slot-scope="scope">
|
<template #avatar="scope">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'profile-id-slug',
|
name: 'profile-id-slug',
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<user-avatar :user="scope.row" size="small" />
|
<user-avatar :user="scope.row" size="small" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
<template slot="name" slot-scope="scope">
|
<template #name="scope">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'profile-id-slug',
|
name: 'profile-id-slug',
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<b>{{ scope.row.name | truncate(20) }}</b>
|
<b>{{ scope.row.name | truncate(20) }}</b>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
<template slot="slug" slot-scope="scope">
|
<template #slug="scope">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'profile-id-slug',
|
name: 'profile-id-slug',
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template slot="unmuteUser" slot-scope="scope">
|
<template #unmuteUser="scope">
|
||||||
<base-button circle size="small" @click="unmuteUser(scope)" icon="user-plus" />
|
<base-button circle size="small" @click="unmuteUser(scope)" icon="user-plus" />
|
||||||
</template>
|
</template>
|
||||||
</ds-table>
|
</ds-table>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user