diff --git a/webapp/components/ContentMenu.vue b/webapp/components/ContentMenu.vue
index 3b82486fe..88dab2b07 100644
--- a/webapp/components/ContentMenu.vue
+++ b/webapp/components/ContentMenu.vue
@@ -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) {
@@ -175,6 +200,7 @@ export default {
},
})
},
+ pinPost() {},
},
}
diff --git a/webapp/components/PostCard/index.vue b/webapp/components/PostCard/index.vue
index 85b19f105..84c5e8073 100644
--- a/webapp/components/PostCard/index.vue
+++ b/webapp/components/PostCard/index.vue
@@ -61,6 +61,8 @@
:resource="post"
:modalsData="menuModalsData"
:is-owner="isAuthor"
+ @pinPost="pinPost"
+ @unpinPost="unpinPost"
/>
@@ -127,6 +129,12 @@ export default {
this.$toast.error(err.message)
}
},
+ pinPost(post) {
+ this.$emit('pinPost', post)
+ },
+ unpinPost(post) {
+ this.$emit('unpinPost', post)
+ },
},
}
diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js
index e0c6e699e..37ec15435 100644
--- a/webapp/graphql/Fragments.js
+++ b/webapp/graphql/Fragments.js
@@ -57,6 +57,12 @@ export const postFragment = lang => gql`
name
icon
}
+ pinnedBy {
+ id
+ name
+ role
+ }
+ pinnedAt
}
`
export const commentFragment = lang => gql`
diff --git a/webapp/graphql/PostMutations.js b/webapp/graphql/PostMutations.js
index fc672c40d..f3528e567 100644
--- a/webapp/graphql/PostMutations.js
+++ b/webapp/graphql/PostMutations.js
@@ -34,6 +34,8 @@ export default () => {
$imageUpload: Upload
$categoryIds: [ID]
$image: String
+ $pinned: Boolean
+ $unpinned: Boolean
) {
UpdatePost(
id: $id
@@ -43,6 +45,8 @@ export default () => {
imageUpload: $imageUpload
categoryIds: $categoryIds
image: $image
+ pinned: $pinned
+ unpinned: $unpinned
) {
id
title
@@ -50,6 +54,11 @@ export default () => {
content
contentExcerpt
language
+ pinnedBy {
+ id
+ name
+ role
+ }
}
}
`,
diff --git a/webapp/locales/en.json b/webapp/locales/en.json
index 9860aa457..189f67bc9 100644
--- a/webapp/locales/en.json
+++ b/webapp/locales/en.json
@@ -368,7 +368,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",
diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue
index 91acb288c..a6f822249 100644
--- a/webapp/pages/index.vue
+++ b/webapp/pages/index.vue
@@ -21,6 +21,8 @@
:post="post"
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
@removePostFromList="deletePost"
+ @pinPost="pinPost"
+ @unpinPost="unpinPost"
/>
@@ -64,6 +66,7 @@ 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().UpdatePost,
+ variables: { id: post.id, title: post.title, content: post.content, pinned: true },
+ })
+ .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().UpdatePost,
+ variables: { id: post.id, title: post.title, content: post.content, unpinned: true },
+ })
+ .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,
}
},
diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue
index 0cb26b62e..e2de1d15e 100644
--- a/webapp/pages/post/_id/_slug/index.vue
+++ b/webapp/pages/post/_id/_slug/index.vue
@@ -18,6 +18,8 @@
:resource="post"
:modalsData="menuModalsData"
:is-owner="isAuthor(post.author ? post.author.id : null)"
+ @pinPost="pinPost"
+ @unpinPost="unpinPost"
/>
@@ -77,6 +79,7 @@