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 @@