feat(webapp): group categories on posts

This commit is contained in:
Moriz Wahl 2023-04-25 12:35:46 +02:00
parent e2667a1a98
commit 3244f3f86d
2 changed files with 264 additions and 248 deletions

View File

@ -10,9 +10,9 @@
:icon="category.icon" :icon="category.icon"
size="small" size="small"
v-tooltip="{ v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`), content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start', placement: 'bottom-start',
}" }"
> >
{{ $t(`contribution.category.name.${category.slug}`) }} {{ $t(`contribution.category.name.${category.slug}`) }}
</base-button> </base-button>
@ -20,75 +20,82 @@
</template> </template>
<script> <script>
import CategoryQuery from '~/graphql/CategoryQuery' import CategoryQuery from '~/graphql/CategoryQuery'
import { CATEGORIES_MAX } from '~/constants/categories.js' import { CATEGORIES_MAX } from '~/constants/categories.js'
import xor from 'lodash/xor' import xor from 'lodash/xor'
import SortCategories from '~/mixins/sortCategoriesMixin.js' import SortCategories from '~/mixins/sortCategoriesMixin.js'
export default { export default {
inject: { inject: {
$parentForm: { $parentForm: {
default: null, default: null,
}, },
}, },
mixins: [SortCategories], mixins: [SortCategories],
props: { props: {
existingCategoryIds: { type: Array, default: () => [] }, existingCategoryIds: { type: Array, default: () => [] },
model: { type: String, required: true }, model: { type: String, required: true },
}, },
data() { data() {
return { return {
categories: null, categories: null,
selectedMax: CATEGORIES_MAX, selectedMax: CATEGORIES_MAX,
selectedCategoryIds: this.existingCategoryIds, selectedCategoryIds: this.existingCategoryIds,
} }
}, },
computed: { watch: {
selectedCount() { existingCategoryIds() {
return this.selectedCategoryIds.length console.log('existingCategoryIds', this.selectedCategoryIds)
}, if (!this.selectedCategoryIds.length)
reachedMaximum() { this.selectedCategoryIds = this.existingCategoryIds
return this.selectedCount >= this.selectedMax },
}, },
}, computed: {
methods: { selectedCount() {
toggleCategory(id) { return this.selectedCategoryIds.length
this.selectedCategoryIds = xor(this.selectedCategoryIds, [id]) },
if (this.$parentForm) { reachedMaximum() {
this.$parentForm.update(this.model, this.selectedCategoryIds) return this.selectedCount >= this.selectedMax
} },
}, },
isActive(id) { methods: {
return this.selectedCategoryIds.includes(id) toggleCategory(id) {
}, this.selectedCategoryIds = xor(this.selectedCategoryIds, [id])
isDisabled(id) { if (this.$parentForm) {
return !!(this.reachedMaximum && !this.isActive(id)) this.$parentForm.update(this.model, this.selectedCategoryIds)
}, }
categoryButtonsId(categoryId) { },
return `category-buttons-${categoryId}` isActive(id) {
}, return this.selectedCategoryIds.includes(id)
}, },
apollo: { isDisabled(id) {
Category: { return !!(this.reachedMaximum && !this.isActive(id))
query() { },
return CategoryQuery() categoryButtonsId(categoryId) {
}, return `category-buttons-${categoryId}`
result({ data: { Category } }) { },
this.categories = this.sortCategories(Category) },
}, apollo: {
}, Category: {
}, query() {
} return CategoryQuery()
},
result({ data: { Category } }) {
this.categories = this.sortCategories(Category)
},
},
},
}
</script> </script>
<style lang="scss"> <style lang="scss">
.categories-select { .categories-select {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
> .base-button { > .base-button {
margin-right: $space-xx-small; margin-right: $space-xx-small;
margin-bottom: $space-xx-small; margin-bottom: $space-xx-small;
} }
} }
</style> </style>

View File

@ -94,188 +94,197 @@
</template> </template>
<script> <script>
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Editor from '~/components/Editor/Editor' import Editor from '~/components/Editor/Editor'
import PostMutations from '~/graphql/PostMutations.js' import PostMutations from '~/graphql/PostMutations.js'
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect' import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
import ImageUploader from '~/components/Uploader/ImageUploader' import ImageUploader from '~/components/Uploader/ImageUploader'
import links from '~/constants/links.js' import links from '~/constants/links.js'
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue' import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
export default { export default {
components: { components: {
Editor, Editor,
ImageUploader, ImageUploader,
PageParamsLink, PageParamsLink,
CategoriesSelect, CategoriesSelect,
}, },
props: { props: {
contribution: { contribution: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
group: { group: {
type: Object, type: Object,
default: () => null, default: () => null,
}, },
}, },
data() { data() {
const { title, content, image, categories } = this.contribution const { title, content, image, categories } = this.contribution
const { const {
sensitive: imageBlurred = false, sensitive: imageBlurred = false,
aspectRatio: imageAspectRatio = null, aspectRatio: imageAspectRatio = null,
type: imageType = null, type: imageType = null,
} = image || {} } = image || {}
return { return {
categoriesActive: this.$env.CATEGORIES_ACTIVE, categoriesActive: this.$env.CATEGORIES_ACTIVE,
links, links,
formData: { formData: {
title: title || '', title: title || '',
content: content || '', content: content || '',
image: image || null, image: image || null,
imageAspectRatio, imageAspectRatio,
imageType, imageType,
imageBlurred, imageBlurred,
categoryIds: categories ? categories.map((category) => category.id) : [], categoryIds: categories ? categories.map((category) => category.id) : []
}, },
formSchema: { formSchema: {
title: { required: true, min: 3, max: 100 }, title: { required: true, min: 3, max: 100 },
content: { required: true }, content: { required: true },
imageBlurred: { required: false }, imageBlurred: { required: false },
categoryIds: { categoryIds: {
type: 'array', type: 'array',
required: this.categoriesActive, required: this.categoriesActive,
validator: (_, value = []) => { validator: (_, value = []) => {
if (this.categoriesActive && (value.length === 0 || value.length > 3)) { if (this.categoriesActive && (value.length === 0 || value.length > 3)) {
return [new Error(this.$t('common.validations.categories'))] return [new Error(this.$t('common.validations.categories'))]
} }
return [] return []
}, },
}, },
}, },
loading: false, loading: false,
users: [], users: [],
hashtags: [], hashtags: [],
imageUpload: null, imageUpload: null,
} }
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
currentUser: 'auth/user', currentUser: 'auth/user',
}), }),
contentLength() { contentLength() {
return this.$filters.removeHtml(this.formData.content).length return this.$filters.removeHtml(this.formData.content).length
}, },
groupId() { groupId() {
return this.group && this.group.id return this.group && this.group.id
}, },
showGroupHint() { showGroupHint() {
return this.groupId && ['closed', 'hidden'].includes(this.group.groupType) return this.groupId && ['closed', 'hidden'].includes(this.group.groupType)
}, },
groupName() { groupName() {
return this.group && this.group.name return this.group && this.group.name
}, },
}, groupCategories() {
methods: { return this.group && this.group.categories
submit() { },
let image = null },
const { title, content, categoryIds } = this.formData watch: {
if (this.formData.image) { groupCategories() {
image = { console.log('groupCategories', this.groupCategories)
sensitive: this.formData.imageBlurred, if (!this.formData.categoryIds && this.groupCategories) this.formData.categoryIds = this.groupCategories
} },
if (this.imageUpload) { },
image.upload = this.imageUpload methods: {
image.aspectRatio = this.formData.imageAspectRatio submit() {
image.type = this.formData.imageType let image = null
} const { title, content, categoryIds } = this.formData
} if (this.formData.image) {
this.loading = true image = {
this.$apollo sensitive: this.formData.imageBlurred,
.mutate({ }
mutation: this.contribution.id ? PostMutations().UpdatePost : PostMutations().CreatePost, if (this.imageUpload) {
variables: { image.upload = this.imageUpload
title, image.aspectRatio = this.formData.imageAspectRatio
content, image.type = this.formData.imageType
categoryIds, }
id: this.contribution.id || null, }
image, this.loading = true
groupId: this.groupId, this.$apollo
}, .mutate({
}) mutation: this.contribution.id ? PostMutations().UpdatePost : PostMutations().CreatePost,
.then(({ data }) => { variables: {
this.loading = false title,
this.$toast.success(this.$t('contribution.success')) content,
const result = data[this.contribution.id ? 'UpdatePost' : 'CreatePost'] categoryIds,
id: this.contribution.id || null,
image,
groupId: this.groupId,
},
})
.then(({ data }) => {
this.loading = false
this.$toast.success(this.$t('contribution.success'))
const result = data[this.contribution.id ? 'UpdatePost' : 'CreatePost']
this.$router.push({ this.$router.push({
name: 'post-id-slug', name: 'post-id-slug',
params: { id: result.id, slug: result.slug }, params: { id: result.id, slug: result.slug },
}) })
}) })
.catch((err) => { .catch((err) => {
this.$toast.error(err.message) this.$toast.error(err.message)
this.loading = false this.loading = false
}) })
}, },
updateEditorContent(value) { updateEditorContent(value) {
this.$refs.contributionForm.update('content', value) this.$refs.contributionForm.update('content', value)
}, },
addHeroImage(file) { addHeroImage(file) {
this.formData.image = null this.formData.image = null
if (file) { if (file) {
const reader = new FileReader() const reader = new FileReader()
reader.onload = ({ target }) => { reader.onload = ({ target }) => {
this.formData.image = { this.formData.image = {
...this.formData.image, ...this.formData.image,
url: target.result, url: target.result,
} }
} }
reader.readAsDataURL(file) reader.readAsDataURL(file)
this.imageUpload = file this.imageUpload = file
} }
}, },
addImageAspectRatio(aspectRatio) { addImageAspectRatio(aspectRatio) {
this.formData.imageAspectRatio = aspectRatio this.formData.imageAspectRatio = aspectRatio
}, },
addImageType(imageType) { addImageType(imageType) {
this.formData.imageType = imageType this.formData.imageType = imageType
}, },
}, },
apollo: { apollo: {
User: { User: {
query() { query() {
return gql` return gql`
query { query {
User(orderBy: slug_asc) { User(orderBy: slug_asc) {
id id
slug slug
} }
} }
` `
}, },
result({ data: { User } }) { result({ data: { User } }) {
this.users = User this.users = User
}, },
}, },
Tag: { Tag: {
query() { query() {
return gql` return gql`
query { query {
Tag(orderBy: id_asc) { Tag(orderBy: id_asc) {
id id
} }
} }
` `
}, },
result({ data: { Tag } }) { result({ data: { Tag } }) {
this.hashtags = Tag this.hashtags = Tag
}, },
}, },
}, },
} }
</script> </script>
<style lang="scss"> <style lang="scss">