diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js
index 837193773..e0b831b59 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.js
+++ b/backend/src/middleware/notifications/notificationsMiddleware.js
@@ -50,7 +50,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
MATCH (user: User)
WHERE user.id in $idsOfUsers
- AND NOT (user)<-[:BLOCKED]-(author)
+ AND NOT (user)-[:BLOCKED]-(author)
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
`
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 (user: User)
WHERE user.id in $idsOfUsers
- AND NOT (user)<-[:BLOCKED]-(author)
- AND NOT (user)<-[:BLOCKED]-(postAuthor)
+ AND NOT (user)-[:BLOCKED]-(author)
+ AND NOT (user)-[:BLOCKED]-(postAuthor)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
`
break
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index 50ec5aa75..755ddabf8 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -102,6 +102,7 @@ export default shield(
PostsEmotionsCountByEmotion: allow,
PostsEmotionsByCurrentUser: isAuthenticated,
mutedUsers: isAuthenticated,
+ blockedUsers: isAuthenticated,
notifications: isAuthenticated,
Donations: isAuthenticated,
},
@@ -139,6 +140,8 @@ export default shield(
RemovePostEmotions: isAuthenticated,
muteUser: isAuthenticated,
unmuteUser: isAuthenticated,
+ blockUser: isAuthenticated,
+ unblockUser: isAuthenticated,
markAsRead: isAuthenticated,
AddEmailAddress: isAuthenticated,
VerifyEmailAddress: isAuthenticated,
diff --git a/backend/src/schema/resolvers/searches.js b/backend/src/schema/resolvers/searches.js
index 5316ccd9a..994d19fa2 100644
--- a/backend/src/schema/resolvers/searches.js
+++ b/backend/src/schema/resolvers/searches.js
@@ -20,7 +20,7 @@ export default {
AND NOT (
author.deleted = true OR author.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,
[(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments,
@@ -40,8 +40,7 @@ export default {
YIELD node as resource, score
MATCH (resource)
WHERE score >= 0.5
- AND NOT (resource.deleted = true OR resource.disabled = true
- OR (:User { id: $thisUserId })-[:BLOCKED]-(resource))
+ AND NOT (resource.deleted = true OR resource.disabled = true)
RETURN resource {.*, __typename: labels(resource)[0]}
LIMIT $limit
`
diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js
index 4af60f014..d1d9111b6 100644
--- a/backend/src/schema/resolvers/users.js
+++ b/backend/src/schema/resolvers/users.js
@@ -23,6 +23,21 @@ export const getMutedUsers = async context => {
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 {
Query: {
mutedUsers: async (object, args, context, resolveInfo) => {
@@ -32,6 +47,13 @@ export default {
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) => {
const { email } = args
if (email) {
@@ -86,7 +108,7 @@ export default {
const unmutedUser = await neode.find('User', params.id)
return unmutedUser.toJson()
},
- block: async (object, args, context, resolveInfo) => {
+ blockUser: async (object, args, context, resolveInfo) => {
const { user: currentUser } = context
if (currentUser.id === args.id) return null
await neode.cypher(
@@ -103,7 +125,7 @@ export default {
await user.relateTo(blockedUser, 'blocked')
return blockedUser.toJson()
},
- unblock: async (object, args, context, resolveInfo) => {
+ unblockUser: async (object, args, context, resolveInfo) => {
const { user: currentUser } = context
if (currentUser.id === args.id) return null
await neode.cypher(
@@ -229,7 +251,7 @@ export default {
boolean: {
followedByCurrentUser:
'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',
isMuted:
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql
index 4eb04a638..71cc1edb0 100644
--- a/backend/src/schema/types/type/User.gql
+++ b/backend/src/schema/types/type/User.gql
@@ -68,10 +68,11 @@ type User {
RETURN COUNT(u) >= 1
"""
)
- isBlocked: Boolean! @cypher(
+
+ blocked: Boolean! @cypher(
statement: """
- MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
- RETURN COUNT(u) >= 1
+ MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
+ RETURN COUNT(user) >= 1
"""
)
@@ -207,6 +208,6 @@ type Mutation {
muteUser(id: ID!): User
unmuteUser(id: ID!): User
- block(id: ID!): User
- unblock(id: ID!): User
+ blockUser(id: ID!): User
+ unblockUser(id: ID!): User
}
diff --git a/cypress/integration/common/search.js b/cypress/integration/common/search.js
index f6589763b..c42ec3ff0 100644
--- a/cypress/integration/common/search.js
+++ b/cypress/integration/common/search.js
@@ -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 => {
expect($li).to.have.length(1);
- });
- cy.get(".ds-select-dropdown").should("contain", 'Nothing found');
+ })
+ cy.get(".ds-select-dropdown")
+ .should("not.have.class", '.search-post')
+ .should("not.contain", 'Spam')
+});
+
+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")
.focus()
.type("{esc}");
-});
+})
Then("I should see the following posts in the select dropdown:", table => {
table.hashes().forEach(({ title }) => {
diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js
index 9a5c02d08..5185c09f9 100644
--- a/cypress/integration/common/steps.js
+++ b/cypress/integration/common/steps.js
@@ -31,6 +31,7 @@ const narratorParams = {
const annoyingParams = {
email: "spammy-spammer@example.org",
+ slug: 'spammy-spammer',
password: "1234",
...termsAndConditionsAgreedVersion
};
@@ -39,8 +40,12 @@ Given("I am logged in", () => {
cy.login(loginCredentials);
});
-Given("I am logged in as the muted user", () => {
- cy.login({ email: annoyingParams.email, password: '1234' });
+Given("the {string} user searches for {string}", (_, postTitle) => {
+ cy.logout()
+ .login({ email: annoyingParams.email, password: '1234' })
+ .get(".searchable-input .ds-select-search")
+ .focus()
+ .type(postTitle);
});
Given("we have a selection of categories", () => {
@@ -123,6 +128,12 @@ When("I visit the {string} page", 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 => {
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")
.factory()
.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");
});
-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(".main-container").contains(
".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 => {
const [firstRow] = table.hashes();
const {
@@ -551,3 +576,11 @@ Then("I see only one post with the title {string}", title => {
.should("have.length", 1);
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.")
+})
\ No newline at end of file
diff --git a/cypress/integration/user_profile/BlockUser.feature b/cypress/integration/user_profile/BlockUser.feature
new file mode 100644
index 000000000..43efe7807
--- /dev/null
+++ b/cypress/integration/user_profile/BlockUser.feature
@@ -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 |
diff --git a/cypress/integration/user_profile/mute-users/Mute.feature b/cypress/integration/user_profile/mute-users/Mute.feature
index b52faeeaa..03ac4370b 100644
--- a/cypress/integration/user_profile/mute-users/Mute.feature
+++ b/cypress/integration/user_profile/mute-users/Mute.feature
@@ -1,8 +1,7 @@
Feature: Mute a User
As a 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:
Given I have a user account
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
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
- 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:
| id | title | content |
| im-not-muted | Post that should be seen | cause I'm not muted |
@@ -36,18 +35,17 @@ Feature: Mute a User
When I mute the user "Spammy Spammer"
And I refresh the page
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"
Then I should see the following posts in the select dropdown:
| title |
| Post that should be seen |
-
+
Scenario: Muted users can still see my posts
Given I previously created a post
And I mute the user "Spammy Spammer"
- Given I log out
- And I am logged in as the muted user
- When I search for "previously created"
+ And the "muted" user searches for "previously created"
Then I should see the following posts in the select dropdown:
| title |
| previously created post |
diff --git a/webapp/components/Comment/Comment.vue b/webapp/components/Comment/Comment.vue
index b413e03f6..78f37c1ea 100644
--- a/webapp/components/Comment/Comment.vue
+++ b/webapp/components/Comment/Comment.vue
@@ -33,7 +33,7 @@
-
'' },
diff --git a/webapp/components/ContentMenu/ContentMenu.vue b/webapp/components/ContentMenu/ContentMenu.vue
index a22bc3267..5ce73c461 100644
--- a/webapp/components/ContentMenu/ContentMenu.vue
+++ b/webapp/components/ContentMenu/ContentMenu.vue
@@ -161,7 +161,7 @@ export default {
callback: () => {
this.$emit('unmute', this.resource)
},
- icon: 'user-plus',
+ icon: 'eye',
})
} else {
routes.push({
@@ -169,6 +169,23 @@ export default {
callback: () => {
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',
})
}
diff --git a/webapp/components/generic/SearchableInput/SearchableInput.vue b/webapp/components/generic/SearchableInput/SearchableInput.vue
index 448c154e0..3260ff082 100644
--- a/webapp/components/generic/SearchableInput/SearchableInput.vue
+++ b/webapp/components/generic/SearchableInput/SearchableInput.vue
@@ -71,7 +71,7 @@ export default {
},
computed: {
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() {
return !isEmpty(this.previousSearchTerm)
@@ -104,7 +104,7 @@ export default {
*/
onEnter(event) {
clearTimeout(this.searchProcess)
- if (!this.pending) {
+ if (!this.loading) {
this.previousSearchTerm = this.unprocessedSearchInput
this.$emit('query', this.unprocessedSearchInput)
}
diff --git a/webapp/graphql/PostQuery.js b/webapp/graphql/PostQuery.js
index c59c894a5..5ddac9c1f 100644
--- a/webapp/graphql/PostQuery.js
+++ b/webapp/graphql/PostQuery.js
@@ -29,6 +29,7 @@ export default i18n => {
...user
...userCounts
...locationAndBadges
+ blocked
}
comments(orderBy: createdAt_asc) {
...comment
diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js
index 8962830dc..a73941794 100644
--- a/webapp/graphql/User.js
+++ b/webapp/graphql/User.js
@@ -24,6 +24,7 @@ export default i18n => {
createdAt
followedByCurrentUser
isMuted
+ blocked
following(first: 7) {
...user
...userCounts
diff --git a/webapp/graphql/settings/BlockedUsers.js b/webapp/graphql/settings/BlockedUsers.js
new file mode 100644
index 000000000..94f2121b1
--- /dev/null
+++ b/webapp/graphql/settings/BlockedUsers.js
@@ -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
+ }
+ }
+ `
+}
diff --git a/webapp/locales/de.json b/webapp/locales/de.json
index d52a9817c..e2416ffd9 100644
--- a/webapp/locales/de.json
+++ b/webapp/locales/de.json
@@ -70,6 +70,11 @@
"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": {
"name": "Einladungen"
},
@@ -143,27 +148,44 @@
"successDelete": "Social-Media gelöscht. Profil aktualisiert!"
},
"muted-users": {
- "name": "Stummgeschaltete Benutzer",
- "explanation": {
- "intro": "Wenn ein anderer Benutzer von dir stummgeschaltet wurde, dann passiert folgendes:",
- "your-perspective": "In deiner Beitragsübersicht tauchen keine Beiträge der stummgeschalteten Person mehr auf.",
- "search": "Die Beiträge von stummgeschalteten Personen verschwinden aus deinen Suchergebnissen."
- },
- "columns": {
- "name": "Name",
- "slug": "Alias",
- "unmute": "Entsperren"
- },
- "empty": "Bislang hast du niemanden stummgeschaltet.",
- "how-to": "Du kannst andere Benutzer auf deren Profilseite über das Inhaltsmenü stummschalten.",
- "mute": "Stumm schalten",
- "unmute": "Stummschaltung aufheben",
- "unmuted": "{name} ist nicht mehr stummgeschaltet"
+ "name": "Stummgeschaltete Benutzer",
+ "explanation": {
+ "intro": "Wenn ein anderer Benutzer von dir stummgeschaltet wurde, dann passiert folgendes:",
+ "your-perspective": "In deiner Beitragsübersicht tauchen keine Beiträge der stummgeschalteten Person mehr auf.",
+ "search": "Die Beiträge von stummgeschalteten Personen verschwinden aus deinen Suchergebnissen."
+ },
+ "columns": {
+ "name": "Name",
+ "slug": "Alias",
+ "unmute": "Entsperren"
+ },
+ "empty": "Bislang hast du niemanden stummgeschaltet.",
+ "how-to": "Du kannst andere Benutzer auf deren Profilseite über das Inhaltsmenü stummschalten.",
+ "mute": "Stumm schalten",
+ "unmute": "Stummschaltung aufheben",
+ "unmuted": "{name} ist nicht mehr stummgeschaltet"
},
- "privacy": {
- "name": "Privatsphäre",
- "make-shouts-public": "Teile von mir empfohlene Artikel öffentlich auf meinem Profil",
- "success-update": "Privatsphäre-Einstellungen gespeichert"
+ "blocked-users": {
+ "name": "Blocked users",
+ "explanation": {
+ "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": {
diff --git a/webapp/locales/en.json b/webapp/locales/en.json
index 1906a5f6f..01f10f4b1 100644
--- a/webapp/locales/en.json
+++ b/webapp/locales/en.json
@@ -329,6 +329,28 @@
"mute": "Mute user",
"unmute": "Unmute user",
"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": {
diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue
index 7c524973c..c8a2d910d 100644
--- a/webapp/pages/post/_id/_slug/index.vue
+++ b/webapp/pages/post/_id/_slug/index.vue
@@ -90,7 +90,17 @@
@toggleNewCommentForm="toggleNewCommentForm"
/>
-
+
+
+ {{ $t('settings.blocked-users.explanation.commenting-disabled') }}
+
+ {{ $t('settings.blocked-users.explanation.commenting-explanation') }}
+ FAQ
+
@@ -103,7 +113,7 @@ import HcHashtag from '~/components/Hashtag/Hashtag'
import ContentMenu from '~/components/ContentMenu/ContentMenu'
import UserTeaser from '~/components/UserTeaser/UserTeaser'
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 { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
import PostQuery from '~/graphql/PostQuery'
@@ -122,7 +132,7 @@ export default {
UserTeaser,
HcShoutButton,
ContentMenu,
- HcCommentForm,
+ CommentForm,
HcCommentList,
HcEmotions,
ContentViewer,
@@ -139,15 +149,10 @@ export default {
title: 'loading',
showNewCommentForm: true,
blurred: false,
+ blocked: null,
+ postAuthor: null,
}
},
- watch: {
- Post(post) {
- this.post = post[0] || {}
- this.title = this.post.title
- this.blurred = this.post.imageBlurred
- },
- },
mounted() {
setTimeout(() => {
// NOTE: quick fix for jumping flexbox implementation
@@ -216,6 +221,12 @@ export default {
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',
},
},
diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue
index ce220b66b..80471fff4 100644
--- a/webapp/pages/profile/_id/_slug.vue
+++ b/webapp/pages/profile/_id/_slug.vue
@@ -24,6 +24,8 @@
class="user-content-menu"
@mute="muteUser"
@unmute="unmuteUser"
+ @block="blockUser"
+ @unblock="unblockUser"
/>
@@ -64,20 +66,21 @@
-
-
-
-
- {{ $t('settings.muted-users.unmute') }}
-
-
-
+
+
+ {{ $t('settings.blocked-users.unblock') }}
+
+
+ {{ $t('settings.muted-users.unmute') }}
+
+
+
@@ -285,6 +288,7 @@ import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
import { profilePagePosts } from '~/graphql/PostQuery'
import UserQuery from '~/graphql/User'
import { muteUser, unmuteUser } from '~/graphql/settings/MutedUsers'
+import { blockUser, unblockUser } from '~/graphql/settings/BlockedUsers'
import PostMutations from '~/graphql/PostMutations'
import UpdateQuery from '~/components/utils/UpdateQuery'
@@ -417,6 +421,24 @@ export default {
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) {
this.$apollo
.mutate({
@@ -559,8 +581,13 @@ export default {
.profile-post-add-button {
box-shadow: $box-shadow-x-large;
}
-.unblock-user-button {
- display: block;
- width: 100%;
+.action-buttons {
+ margin: $space-small 0;
+
+ > .base-button {
+ display: block;
+ width: 100%;
+ margin-bottom: $space-x-small;
+ }
}
diff --git a/webapp/pages/settings.vue b/webapp/pages/settings.vue
index 5684da024..950652028 100644
--- a/webapp/pages/settings.vue
+++ b/webapp/pages/settings.vue
@@ -43,6 +43,10 @@ export default {
name: this.$t('settings.muted-users.name'),
path: `/settings/muted-users`,
},
+ {
+ name: this.$t('settings.blocked-users.name'),
+ path: `/settings/blocked-users`,
+ },
{
name: this.$t('settings.embeds.name'),
path: `/settings/embeds`,
diff --git a/webapp/pages/settings/blocked-users.spec.js b/webapp/pages/settings/blocked-users.spec.js
new file mode 100644
index 000000000..d5258770b
--- /dev/null
+++ b/webapp/pages/settings/blocked-users.spec.js
@@ -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'] = ''
+
+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' },
+ })
+ })
+ })
+ })
+ })
+})
diff --git a/webapp/pages/settings/blocked-users.vue b/webapp/pages/settings/blocked-users.vue
new file mode 100644
index 000000000..006d16194
--- /dev/null
+++ b/webapp/pages/settings/blocked-users.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+ {{ $t('settings.blocked-users.explanation.intro') }}
+
+
+
+ {{ $t('settings.blocked-users.explanation.your-perspective') }}
+
+
+ {{ $t('settings.blocked-users.explanation.their-perspective') }}
+
+
+ {{ $t('settings.blocked-users.explanation.notifications') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.name | truncate(20) }}
+
+
+
+
+ {{ scope.row.slug | truncate(20) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('settings.blocked-users.empty') }}
+
+
+
+
+ {{ $t('settings.blocked-users.how-to') }}
+
+
+
+
+
+
+
+
+
diff --git a/webapp/pages/settings/muted-users.spec.js b/webapp/pages/settings/muted-users.spec.js
index 7d536e45c..c4ebcf4ea 100644
--- a/webapp/pages/settings/muted-users.spec.js
+++ b/webapp/pages/settings/muted-users.spec.js
@@ -46,7 +46,7 @@ describe('muted-users.vue', () => {
expect(wrapper.is('div')).toBe(true)
})
- describe('given a list of blocked users', () => {
+ describe('given a list of muted users', () => {
beforeEach(() => {
const mutedUsers = [{ id: 'u1', name: 'John Doe', slug: 'john-doe', avatar: '' }]
wrapper.setData({ mutedUsers })
@@ -54,7 +54,7 @@ describe('muted-users.vue', () => {
describe('click unmute', () => {
beforeEach(() => {
- wrapper.find('button').trigger('click')
+ wrapper.find('.base-button').trigger('click')
})
it('calls unmute mutation with given user', () => {
diff --git a/webapp/pages/settings/muted-users.vue b/webapp/pages/settings/muted-users.vue
index b33c0e571..630877eea 100644
--- a/webapp/pages/settings/muted-users.vue
+++ b/webapp/pages/settings/muted-users.vue
@@ -17,7 +17,7 @@
-
+
-
+
+
+