Start setting up frontend pinning/unpinning

- Add pin/unpin post to content menu
- Update apollo cache to reactively unpin
- Update apollo cache in root path to re-order Posts
- Order with pinned post first
- Start setting up filters, so that the pinned post is always the first
post visible
This commit is contained in:
mattwr18 2019-10-16 23:03:45 +02:00
parent 36f6be9e36
commit f871df02ae
9 changed files with 170 additions and 30 deletions

View File

@ -55,24 +55,46 @@ export default {
routes() { routes() {
let routes = [] let routes = []
if (this.isOwner && this.resourceType === 'contribution') { if (this.resourceType === 'contribution') {
routes.push({ if (this.isOwner) {
name: this.$t(`post.menu.edit`), routes.push({
path: this.$router.resolve({ name: this.$t(`post.menu.edit`),
name: 'post-edit-id', path: this.$router.resolve({
params: { name: 'post-edit-id',
id: this.resource.id, params: {
id: this.resource.id,
},
}).href,
icon: 'edit',
})
routes.push({
name: this.$t(`post.menu.delete`),
callback: () => {
this.openModal('delete')
}, },
}).href, icon: 'trash',
icon: 'edit', })
}) }
routes.push({
name: this.$t(`post.menu.delete`), if (this.isAdmin) {
callback: () => { if (!this.resource.pinnedBy) {
this.openModal('delete') routes.push({
}, name: this.$t(`post.menu.pin`),
icon: 'trash', 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') { if (this.isOwner && this.resourceType === 'comment') {
@ -155,6 +177,9 @@ export default {
isModerator() { isModerator() {
return this.$store.getters['auth/isModerator'] return this.$store.getters['auth/isModerator']
}, },
isAdmin() {
return this.$store.getters['auth/isAdmin']
},
}, },
methods: { methods: {
openItem(route, toggleMenu) { openItem(route, toggleMenu) {
@ -175,6 +200,7 @@ export default {
}, },
}) })
}, },
pinPost() {},
}, },
} }
</script> </script>

View File

@ -61,6 +61,8 @@
:resource="post" :resource="post"
:modalsData="menuModalsData" :modalsData="menuModalsData"
:is-owner="isAuthor" :is-owner="isAuthor"
@pinPost="pinPost"
@unpinPost="unpinPost"
/> />
</div> </div>
</client-only> </client-only>
@ -127,6 +129,12 @@ export default {
this.$toast.error(err.message) this.$toast.error(err.message)
} }
}, },
pinPost(post) {
this.$emit('pinPost', post)
},
unpinPost(post) {
this.$emit('unpinPost', post)
},
}, },
} }
</script> </script>

View File

@ -57,6 +57,12 @@ export const postFragment = lang => gql`
name name
icon icon
} }
pinnedBy {
id
name
role
}
pinnedAt
} }
` `
export const commentFragment = lang => gql` export const commentFragment = lang => gql`

View File

@ -34,6 +34,8 @@ export default () => {
$imageUpload: Upload $imageUpload: Upload
$categoryIds: [ID] $categoryIds: [ID]
$image: String $image: String
$pinned: Boolean
$unpinned: Boolean
) { ) {
UpdatePost( UpdatePost(
id: $id id: $id
@ -43,6 +45,8 @@ export default () => {
imageUpload: $imageUpload imageUpload: $imageUpload
categoryIds: $categoryIds categoryIds: $categoryIds
image: $image image: $image
pinned: $pinned
unpinned: $unpinned
) { ) {
id id
title title
@ -50,6 +54,11 @@ export default () => {
content content
contentExcerpt contentExcerpt
language language
pinnedBy {
id
name
role
}
} }
} }
`, `,

View File

@ -368,7 +368,11 @@
}, },
"menu": { "menu": {
"edit": "Edit Post", "edit": "Edit Post",
"delete": "Delete Post" "delete": "Delete Post",
"pin": "Pin post",
"pinnedSuccessfully": "Post pinned successfully!",
"unpin": "Unpin post",
"unpinnedSuccessfully": "Post unpinned successfully!"
}, },
"comment": { "comment": {
"submit": "Comment", "submit": "Comment",

View File

@ -21,6 +21,8 @@
:post="post" :post="post"
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }" :width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
@removePostFromList="deletePost" @removePostFromList="deletePost"
@pinPost="pinPost"
@unpinPost="unpinPost"
/> />
</masonry-grid-item> </masonry-grid-item>
</template> </template>
@ -64,6 +66,7 @@ import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue' import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { filterPosts } from '~/graphql/PostQuery.js' import { filterPosts } from '~/graphql/PostQuery.js'
import PostMutations from '~/graphql/PostMutations'
export default { export default {
components: { components: {
@ -166,6 +169,37 @@ export default {
return post.id !== deletedPost.id 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: { apollo: {
Post: { Post: {
@ -176,7 +210,7 @@ export default {
return { return {
filter: this.finalFilters, filter: this.finalFilters,
first: this.pageSize, first: this.pageSize,
orderBy: this.sorting, orderBy: ['pinnedAt_asc', this.sorting],
offset: 0, offset: 0,
} }
}, },

View File

@ -18,6 +18,8 @@
:resource="post" :resource="post"
:modalsData="menuModalsData" :modalsData="menuModalsData"
:is-owner="isAuthor(post.author ? post.author.id : null)" :is-owner="isAuthor(post.author ? post.author.id : null)"
@pinPost="pinPost"
@unpinPost="unpinPost"
/> />
</client-only> </client-only>
<ds-space margin-bottom="small" /> <ds-space margin-bottom="small" />
@ -77,6 +79,7 @@
</template> </template>
<script> <script>
import { mapMutations } from 'vuex'
import ContentViewer from '~/components/Editor/ContentViewer' import ContentViewer from '~/components/Editor/ContentViewer'
import HcCategory from '~/components/Category' import HcCategory from '~/components/Category'
import HcHashtag from '~/components/Hashtag/Hashtag' import HcHashtag from '~/components/Hashtag/Hashtag'
@ -88,6 +91,7 @@ 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'
import HcEmotions from '~/components/Emotions/Emotions' import HcEmotions from '~/components/Emotions/Emotions'
import PostMutations from '~/graphql/PostMutations'
export default { export default {
name: 'PostSlug', name: 'PostSlug',
@ -141,6 +145,9 @@ export default {
}, },
}, },
methods: { methods: {
...mapMutations({
setCurrentUser: 'auth/SET_USER',
}),
isAuthor(id) { isAuthor(id) {
return this.$store.getters['auth/user'].id === id return this.$store.getters['auth/user'].id === id
}, },
@ -156,6 +163,33 @@ export default {
async createComment(comment) { async createComment(comment) {
this.post.comments.push(comment) this.post.comments.push(comment)
}, },
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'))
})
.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'))
})
.catch(error => this.$toast.error(error.message))
},
}, },
apollo: { apollo: {
Post: { Post: {

View File

@ -444,7 +444,7 @@ export default {
filter: this.filter, filter: this.filter,
first: this.pageSize, first: this.pageSize,
offset: 0, offset: 0,
orderBy: 'createdAt_desc', orderBy: ['pinnedAt_asc', 'createdAt_desc'],
} }
}, },
update({ Post }) { update({ Post }) {

View File

@ -10,23 +10,31 @@ const defaultFilter = {}
export const state = () => { export const state = () => {
return { return {
filter: { filter: {
...defaultFilter, OR: [{ pinnedBy_in: { role_in: ['admin'] } }, {}],
}, },
} }
} }
export const mutations = { export const mutations = {
TOGGLE_FILTER_BY_FOLLOWED(state, currentUserId) { TOGGLE_FILTER_BY_FOLLOWED(state, currentUserId) {
const filter = clone(state.filter) let filter = clone(state.filter)
const id = get(filter, 'author.followedBy_some.id') const id = filter.OR.find(object => object.author)
if (id) { if (id) {
delete filter.author filter.OR.forEach(object => delete object.author)
state.filter = filter state.filter = filter
} else { } else {
state.filter = { if (isEmpty(filter.OR[-1])) filter.OR.pop()
...filter, filter.OR.map(object => {
for (let key in object) {
if (object.hasOwnProperty(key)) {
object = { key: object[key] }
}
}
})
filter.OR.unshift({
author: { followedBy_some: { id: currentUserId } }, author: { followedBy_some: { id: currentUserId } },
} })
state.filter = filter
} }
}, },
RESET_CATEGORIES(state) { RESET_CATEGORIES(state) {
@ -35,9 +43,20 @@ export const mutations = {
state.filter = filter state.filter = filter
}, },
TOGGLE_CATEGORY(state, categoryId) { TOGGLE_CATEGORY(state, categoryId) {
const filter = clone(state.filter) let filter = clone(state.filter)
update(filter, 'categories_some.id_in', categoryIds => xor(categoryIds, [categoryId])) if (isEmpty(filter.OR[-1])) filter.OR.pop()
if (isEmpty(get(filter, 'categories_some.id_in'))) delete filter.categories_some filter.OR.map(object => {
for (let key in object) {
if (object.hasOwnProperty(key)) {
object = { key: object[key] }
}
}
})
filter.OR.unshift({
categories_some: { id_in: [categoryId] },
})
// update(filter, 'categories_some.id_in', categoryIds => xor(categoryIds, [categoryId]))
// if (isEmpty(get(filter.OR[0], 'categories_some.id_in'))) delete filter.OR[0].categories_some
state.filter = filter state.filter = filter
}, },
TOGGLE_EMOTION(state, emotion) { TOGGLE_EMOTION(state, emotion) {