mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into 5059-groups/5131-implement-group-gql-model-and-crud
# Conflicts: # backend/.env.template # webapp/.env.template
This commit is contained in:
commit
2107dd4be8
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,8 +1,10 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
name: "\U0001F41B Bug Report"
|
||||
about: Create a report to help us to improve.
|
||||
title: "\U0001F41B [Bug] XXX"
|
||||
labels: bug
|
||||
title: 🐛 [Bug]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## :bug: Bug Report
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/devops_ticket.md
vendored
6
.github/ISSUE_TEMPLATE/devops_ticket.md
vendored
@ -1,8 +1,10 @@
|
||||
---
|
||||
name: 💥 DevOps Ticket
|
||||
name: "\U0001F4A5 DevOps Ticket"
|
||||
about: Help us manage our deployed app.
|
||||
title: "\U0001F4A5 [DevOps] XXX"
|
||||
labels: devops
|
||||
title: 💥 [DevOps]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 💥 DevOps Ticket
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/epic.md
vendored
7
.github/ISSUE_TEMPLATE/epic.md
vendored
@ -1,9 +1,12 @@
|
||||
---
|
||||
name: 🌟 Epic
|
||||
name: "\U0001F31F Epic"
|
||||
about: Define a big development step.
|
||||
title: "\U0001F31F [EPIC] XXX"
|
||||
labels: epic
|
||||
title: 🌟 [EPIC]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- THIS ISSUE-TYPE IS NOT FOR YOU! -->
|
||||
<!-- If you need an answer right away, visit the ocelot.social Discord:
|
||||
https://discord.gg/AJSX9DCSUA -->
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,8 +1,10 @@
|
||||
---
|
||||
name: 🚀 Feature Request
|
||||
name: "\U0001F680 Feature Request"
|
||||
about: Suggest an idea for this project.
|
||||
title: "\U0001F680 [Feature] XXX"
|
||||
labels: feature
|
||||
title: 🚀 [Feature]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## :rocket: Feature Request
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/question.md
vendored
7
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,9 +1,12 @@
|
||||
---
|
||||
name: 💬 Question
|
||||
name: "\U0001F4AC Question"
|
||||
about: If you need help understanding ocelot.social.
|
||||
title: "\U0001F4AC [Question] XXX"
|
||||
labels: question
|
||||
title: 💬 [Question]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Chat with ocelot.social team -->
|
||||
<!-- If you need an answer right away, visit the ocelot.social Discord:
|
||||
https://discord.gg/AJSX9DCSUA -->
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/refactor_tickets.md
vendored
7
.github/ISSUE_TEMPLATE/refactor_tickets.md
vendored
@ -1,10 +1,11 @@
|
||||
---
|
||||
name: 🔧 Refactor
|
||||
name: "\U0001F527 Refactor"
|
||||
about: Help us improve our code by refactoring it.
|
||||
title: "\U0001F527 [Refactor] XXX"
|
||||
labels: refactor
|
||||
title: 🔧 [Refactor]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Refactor
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { UserInputError } from 'apollo-server'
|
||||
import { mergeImage, deleteImage } from './images/images'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
|
||||
import CONFIG from '../../config'
|
||||
|
||||
const maintainPinnedPosts = (params) => {
|
||||
const pinnedPostFilter = { pinned: true }
|
||||
@ -76,12 +77,20 @@ export default {
|
||||
},
|
||||
Mutation: {
|
||||
CreatePost: async (_parent, params, context, _resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
const { image: imageInput } = params
|
||||
delete params.categoryIds
|
||||
delete params.image
|
||||
params.id = params.id || uuid()
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const categoriesCypher =
|
||||
CONFIG.CATEGORIES_ACTIVE && categoryIds
|
||||
? `WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (post)-[:CATEGORIZED]->(category)`
|
||||
: ''
|
||||
const createPostTransactionResponse = await transaction.run(
|
||||
`
|
||||
CREATE (post:Post)
|
||||
@ -93,9 +102,10 @@ export default {
|
||||
WITH post
|
||||
MATCH (author:User {id: $userId})
|
||||
MERGE (post)<-[:WROTE]-(author)
|
||||
${categoriesCypher}
|
||||
RETURN post {.*}
|
||||
`,
|
||||
{ userId: context.user.id, params },
|
||||
{ userId: context.user.id, params, categoryIds },
|
||||
)
|
||||
const [post] = createPostTransactionResponse.records.map((record) => record.get('post'))
|
||||
if (imageInput) {
|
||||
@ -127,7 +137,7 @@ export default {
|
||||
WITH post
|
||||
`
|
||||
|
||||
if (categoryIds && categoryIds.length) {
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
|
||||
@ -58,6 +58,9 @@ describe('ContributionForm.vue', () => {
|
||||
back: jest.fn(),
|
||||
push: jest.fn(),
|
||||
},
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
propsData = {}
|
||||
})
|
||||
@ -132,6 +135,7 @@ describe('ContributionForm.vue', () => {
|
||||
variables: {
|
||||
title: postTitle,
|
||||
content: postContent,
|
||||
categoryIds: [],
|
||||
id: null,
|
||||
image: null,
|
||||
},
|
||||
@ -254,6 +258,7 @@ describe('ContributionForm.vue', () => {
|
||||
variables: {
|
||||
title: propsData.contribution.title,
|
||||
content: propsData.contribution.content,
|
||||
categoryIds: [],
|
||||
id: propsData.contribution.id,
|
||||
image: {
|
||||
sensitive: false,
|
||||
|
||||
@ -51,6 +51,19 @@
|
||||
{{ contentLength }}
|
||||
<base-icon v-if="errors && errors.content" name="warning" />
|
||||
</ds-chip>
|
||||
<categories-select
|
||||
v-if="categoriesActive"
|
||||
model="categoryIds"
|
||||
:existingCategoryIds="formData.categoryIds"
|
||||
/>
|
||||
<ds-chip
|
||||
v-if="categoriesActive"
|
||||
size="base"
|
||||
:color="errors && errors.categoryIds && 'danger'"
|
||||
>
|
||||
{{ formData.categoryIds.length }} / 3
|
||||
<base-icon v-if="errors && errors.categoryIds" name="warning" />
|
||||
</ds-chip>
|
||||
<div class="buttons">
|
||||
<base-button data-test="cancel-button" :disabled="loading" @click="$router.back()" danger>
|
||||
{{ $t('actions.cancel') }}
|
||||
@ -69,6 +82,7 @@ import gql from 'graphql-tag'
|
||||
import { mapGetters } from 'vuex'
|
||||
import HcEditor from '~/components/Editor/Editor'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||
import ImageUploader from '~/components/ImageUploader/ImageUploader'
|
||||
import links from '~/constants/links.js'
|
||||
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
|
||||
@ -78,6 +92,7 @@ export default {
|
||||
HcEditor,
|
||||
ImageUploader,
|
||||
PageParamsLink,
|
||||
CategoriesSelect,
|
||||
},
|
||||
props: {
|
||||
contribution: {
|
||||
@ -86,7 +101,7 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const { title, content, image } = this.contribution
|
||||
const { title, content, image, categories } = this.contribution
|
||||
const {
|
||||
sensitive: imageBlurred = false,
|
||||
aspectRatio: imageAspectRatio = null,
|
||||
@ -94,6 +109,7 @@ export default {
|
||||
} = image || {}
|
||||
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
links,
|
||||
formData: {
|
||||
title: title || '',
|
||||
@ -102,11 +118,22 @@ export default {
|
||||
imageAspectRatio,
|
||||
imageType,
|
||||
imageBlurred,
|
||||
categoryIds: categories ? categories.map((category) => category.id) : [],
|
||||
},
|
||||
formSchema: {
|
||||
title: { required: true, min: 3, max: 100 },
|
||||
content: { required: true },
|
||||
imageBlurred: { required: false },
|
||||
categoryIds: {
|
||||
type: 'array',
|
||||
required: this.categoriesActive,
|
||||
validator: (_, value = []) => {
|
||||
if (this.categoriesActive && (value.length === 0 || value.length > 3)) {
|
||||
return [new Error(this.$t('common.validations.categories'))]
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
users: [],
|
||||
@ -125,7 +152,7 @@ export default {
|
||||
methods: {
|
||||
submit() {
|
||||
let image = null
|
||||
const { title, content } = this.formData
|
||||
const { title, content, categoryIds } = this.formData
|
||||
if (this.formData.image) {
|
||||
image = {
|
||||
sensitive: this.formData.imageBlurred,
|
||||
@ -143,6 +170,7 @@ export default {
|
||||
variables: {
|
||||
title,
|
||||
content,
|
||||
categoryIds,
|
||||
id: this.contribution.id || null,
|
||||
image,
|
||||
},
|
||||
|
||||
@ -47,6 +47,9 @@ describe('PostTeaser', () => {
|
||||
data: { DeletePost: { id: 'deleted-post-id' } },
|
||||
}),
|
||||
},
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
getters = {
|
||||
'auth/isModerator': () => false,
|
||||
|
||||
@ -26,7 +26,19 @@
|
||||
class="footer"
|
||||
v-observe-visibility="(isVisible, entry) => visibilityChanged(isVisible, entry, post.id)"
|
||||
>
|
||||
<div class="categories-placeholder"></div>
|
||||
<div class="categories" v-if="categoriesActive">
|
||||
<hc-category
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
v-tooltip="{
|
||||
content: $t(`contribution.category.name.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
:icon="category.icon"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="categories-placeholder"></div>
|
||||
<counter-icon
|
||||
icon="bullhorn"
|
||||
:count="post.shoutedCount"
|
||||
@ -70,6 +82,7 @@
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import HcRibbon from '~/components/Ribbon'
|
||||
import HcCategory from '~/components/Category'
|
||||
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
||||
import { mapGetters } from 'vuex'
|
||||
import PostMutations from '~/graphql/PostMutations'
|
||||
@ -79,6 +92,7 @@ export default {
|
||||
name: 'PostTeaser',
|
||||
components: {
|
||||
UserTeaser,
|
||||
HcCategory,
|
||||
HcRibbon,
|
||||
ContentMenu,
|
||||
CounterIcon,
|
||||
@ -93,6 +107,11 @@ export default {
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const { image } = this.post
|
||||
if (!image) return
|
||||
|
||||
@ -24,6 +24,9 @@ describe('SearchResults', () => {
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
|
||||
@ -78,6 +78,12 @@ export const tagsCategoriesAndPinnedFragment = gql`
|
||||
tags {
|
||||
id
|
||||
}
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
pinnedBy {
|
||||
id
|
||||
name
|
||||
|
||||
@ -18,6 +18,9 @@ describe('post/_id.vue', () => {
|
||||
slug: 'my-post',
|
||||
},
|
||||
},
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -72,6 +72,9 @@ describe('PostSlug', () => {
|
||||
query: jest.fn().mockResolvedValue({ data: { PostEmotionsCountByEmotion: {} } }),
|
||||
},
|
||||
$scrollTo: jest.fn(),
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
stubs = {
|
||||
HcEditor: { render: () => {}, methods: { insertReply: jest.fn(() => null) } },
|
||||
|
||||
@ -44,6 +44,19 @@
|
||||
<h2 class="title hyphenate-text">{{ post.title }}</h2>
|
||||
<ds-space margin-bottom="small" />
|
||||
<content-viewer class="content hyphenate-text" :content="post.content" />
|
||||
<!-- Categories -->
|
||||
<div v-if="categoriesActive" class="categories">
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<ds-space margin="xx-large" />
|
||||
<ds-space margin="xx-small" />
|
||||
<hc-category
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
:icon="category.icon"
|
||||
:name="$t(`contribution.category.name.${category.slug}`)"
|
||||
/>
|
||||
</div>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Tags -->
|
||||
<div v-if="post.tags && post.tags.length" class="tags">
|
||||
<ds-space margin="xx-small" />
|
||||
@ -91,6 +104,7 @@
|
||||
|
||||
<script>
|
||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import HcCategory from '~/components/Category'
|
||||
import HcHashtag from '~/components/Hashtag/Hashtag'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
@ -118,6 +132,7 @@ export default {
|
||||
CommentForm,
|
||||
CommentList,
|
||||
ContentViewer,
|
||||
HcCategory,
|
||||
HcHashtag,
|
||||
HcShoutButton,
|
||||
PageParamsLink,
|
||||
@ -138,6 +153,7 @@ export default {
|
||||
blurred: false,
|
||||
blocked: null,
|
||||
postAuthor: null,
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -171,12 +187,12 @@ export default {
|
||||
heroImageStyle() {
|
||||
/* Return false when image property is not present or is not a number
|
||||
so no unnecessary css variables are set.
|
||||
*/
|
||||
*/
|
||||
|
||||
if (!this.post.image || typeof this.post.image.aspectRatio !== 'number') return false
|
||||
/* Return the aspect ratio as a css variable. Later to be used when calculating
|
||||
the height with respect to the width.
|
||||
*/
|
||||
*/
|
||||
return {
|
||||
'--hero-image-aspect-ratio': 1.0 / this.post.image.aspectRatio,
|
||||
}
|
||||
@ -253,12 +269,12 @@ export default {
|
||||
/* The padding top makes sure the correct height is set (according to the
|
||||
hero image aspect ratio) before the hero image loads so
|
||||
the autoscroll works correctly when following a comment link.
|
||||
*/
|
||||
*/
|
||||
|
||||
padding-top: calc(var(--hero-image-aspect-ratio) * (100% + 48px));
|
||||
/* Letting the image fill the container, since the container
|
||||
is the one determining height
|
||||
*/
|
||||
*/
|
||||
> .image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@ -5,13 +5,13 @@ const localVue = global.localVue
|
||||
|
||||
describe('create.vue', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
})
|
||||
const mocks = {
|
||||
$t: jest.fn(),
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
|
||||
@ -34,6 +34,9 @@ describe('post/_id.vue', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: false,
|
||||
},
|
||||
}
|
||||
store = new Vuex.Store({
|
||||
getters: {
|
||||
|
||||
@ -29,6 +29,10 @@ describe('Registration', () => {
|
||||
query: {},
|
||||
},
|
||||
$env: {},
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn(),
|
||||
},
|
||||
}
|
||||
asyncData = false
|
||||
isLoggedIn = false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user