mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into 5059-epic-groups
# Conflicts: # backend/src/schema/types/type/User.gql
This commit is contained in:
commit
89920f387a
@ -64,6 +64,7 @@ your `.env` configuration file.
|
||||
Your backend is up and running at [http://localhost:4000/](http://localhost:4000/)
|
||||
This will start the GraphQL service \(by default on localhost:4000\) where you
|
||||
can issue GraphQL requests or access GraphQL Playground in the browser.
|
||||
More details about our GraphQL playground and how to use it with ocelot.social can be found [here](./src/graphql/GraphQL-Playground.md).
|
||||
|
||||

|
||||
|
||||
|
||||
108
backend/src/graphql/GraphQL-Playground.md
Normal file
108
backend/src/graphql/GraphQL-Playground.md
Normal file
@ -0,0 +1,108 @@
|
||||
# GraphQL Playground
|
||||
|
||||
To use GraphQL Playground, we need to know some basics:
|
||||
|
||||
## How To Login?
|
||||
|
||||
First, we need to have a user from ocelot.social to log in as.
|
||||
The user can be created by seeding the Neo4j database from the backend or by multiple GraphQL mutations.
|
||||
|
||||
### Seed The Neo4j Database
|
||||
|
||||
In your browser you can reach the GraphQL Playground under `http://localhost:4000/`, if the database and the backend are running, see [backend](../../README.md).
|
||||
There you will also find instructions on how to seed the database.
|
||||
|
||||
### Use GraphQL Mutations To Create A User
|
||||
|
||||
TODO: Describe how to create a user using GraphQL mutations!
|
||||
|
||||
### Login Via GraphQL
|
||||
|
||||
You can register a user by sending the query:
|
||||
|
||||
```gql
|
||||
mutation {
|
||||
login(email: "user@example.org", password: "1234")
|
||||
}
|
||||
```
|
||||
|
||||
Or use `"moderator@example.org"` or `"admin@example.org"` for the roll you need.
|
||||
|
||||
If all goes well, you will receive a QGL response like:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"login": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTY2MjAyMzMwNSwiZXhwIjoxNzI1MTM4NTA1LCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.atBS-SOeS784HPeFl_5s8sRWehEAU1BkgcOZFD8d4bU"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use this response to set an HTTP header when you click `HTTP HEADERS` in the footer.
|
||||
Just set it with the login token you received in response:
|
||||
|
||||
```json
|
||||
{
|
||||
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTY2MjAyMzMwNSwiZXhwIjoxNzI1MTM4NTA1LCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.atBS-SOeS784HPeFl_5s8sRWehEAU1BkgcOZFD8d4bU"
|
||||
}
|
||||
```
|
||||
|
||||
This token is used for all other queries and mutations you send to the backend.
|
||||
|
||||
## Query And Mutate
|
||||
|
||||
When you are logged in and open a new playground tab by clicking "+", you can create a new group by sending the following mutation:
|
||||
|
||||
```gql
|
||||
mutation {
|
||||
CreateGroup(
|
||||
# id: ""
|
||||
name: "My Group"
|
||||
# slug: ""
|
||||
about: "We will save the world"
|
||||
description: "<p class=\"\"><em>English:</em></p><p class=\"\">This group is hidden.</p><h3>What is our group for?</h3><p>This group was created to allow investigative journalists to share and collaborate.</p><h3>How does it work?</h3><p>Here you can internally share posts and comments about them.</p><p><br></p><p><em>Deutsch:</em></p><p class=\"\">Diese Gruppe ist verborgen.</p><h3>Wofür ist unsere Gruppe?</h3><p class=\"\">Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.</p><h3>Wie funktioniert das?</h3><p class=\"\">Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.</p>"
|
||||
groupType: hidden
|
||||
actionRadius: interplanetary
|
||||
categoryIds: ["cat12"]
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
myRole
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You will receive the answer:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"CreateGroup": {
|
||||
"id": "2e3bbadb-804b-4ebc-a673-2d7c7f05e827",
|
||||
"name": "My Group",
|
||||
"slug": "my-group",
|
||||
"createdAt": "2022-09-01T09:44:47.969Z",
|
||||
"updatedAt": "2022-09-01T09:44:47.969Z",
|
||||
"disabled": false,
|
||||
"deleted": false,
|
||||
"about": "We will save the world",
|
||||
"description": "<p class=\"\"><em>English:</em></p><p class=\"\">This group is hidden.</p><h3>What is our group for?</h3><p>This group was created to allow investigative journalists to share and collaborate.</p><h3>How does it work?</h3><p>Here you can internally share posts and comments about them.</p><p><br></p><p><em>Deutsch:</em></p><p class=\"\">Diese Gruppe ist verborgen.</p><h3>Wofür ist unsere Gruppe?</h3><p class=\"\">Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.</p><h3>Wie funktioniert das?</h3><p class=\"\">Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.</p>",
|
||||
"groupType": "hidden",
|
||||
"actionRadius": "interplanetary",
|
||||
"myRole": "owner"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you look into the Neo4j database with your browser and search the groups, you will now also find your new group.
|
||||
For more details about our Neo4j database read [here](../../../neo4j/README.md).
|
||||
@ -323,6 +323,7 @@ export default shield(
|
||||
GenerateInviteCode: isAuthenticated,
|
||||
switchUserRole: isAdmin,
|
||||
markTeaserAsViewed: allow,
|
||||
saveCategorySettings: isAuthenticated,
|
||||
},
|
||||
User: {
|
||||
email: or(isMyOwn, isAdmin),
|
||||
|
||||
@ -269,6 +269,45 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
saveCategorySettings: async (object, args, context, resolveInfo) => {
|
||||
const { activeCategories } = args
|
||||
const {
|
||||
user: { id },
|
||||
} = context
|
||||
|
||||
const session = context.driver.session()
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(category:Category)
|
||||
DELETE previousCategories
|
||||
RETURN user, category
|
||||
`,
|
||||
{ id },
|
||||
)
|
||||
})
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const saveCategorySettingsResponse = await transaction.run(
|
||||
`
|
||||
MATCH (category:Category) WHERE NOT category.id IN $activeCategories
|
||||
MATCH (user:User { id: $id })
|
||||
MERGE (user)-[r:NOT_INTERESTED_IN]->(category)
|
||||
RETURN user, r, category
|
||||
`,
|
||||
{ id, activeCategories },
|
||||
)
|
||||
const [user] = await saveCategorySettingsResponse.records.map((record) =>
|
||||
record.get('user'),
|
||||
)
|
||||
return user
|
||||
})
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
return true
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
User: {
|
||||
email: async (parent, params, context, resolveInfo) => {
|
||||
|
||||
@ -3,6 +3,7 @@ import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { categories } from '../../constants/categories'
|
||||
|
||||
const categoryIds = ['cat9']
|
||||
let user
|
||||
@ -56,6 +57,12 @@ const switchUserRoleMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const saveCategorySettings = gql`
|
||||
mutation ($activeCategories: [String]) {
|
||||
saveCategorySettings(activeCategories: $activeCategories)
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
@ -544,3 +551,146 @@ describe('switch user role', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('save category settings', () => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all(
|
||||
categories.map(({ icon, name }, index) => {
|
||||
Factory.build('category', {
|
||||
id: `cat${index + 1}`,
|
||||
slug: name,
|
||||
name,
|
||||
icon,
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await Factory.build('user', {
|
||||
id: 'user',
|
||||
role: 'user',
|
||||
})
|
||||
variables = {
|
||||
activeCategories: ['cat1', 'cat3', 'cat5'],
|
||||
}
|
||||
})
|
||||
|
||||
describe('not authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = undefined
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
const userQuery = gql`
|
||||
query ($id: ID) {
|
||||
User(id: $id) {
|
||||
activeCategories {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('no categories saved', () => {
|
||||
it('returns true for active categories mutation', async () => {
|
||||
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { saveCategorySettings: true },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('query for user', () => {
|
||||
beforeEach(async () => {
|
||||
await mutate({ mutation: saveCategorySettings, variables })
|
||||
})
|
||||
|
||||
it('returns the active categories when user is queried', async () => {
|
||||
await expect(
|
||||
query({ query: userQuery, variables: { id: authenticatedUser.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
User: [
|
||||
{
|
||||
activeCategories: expect.arrayContaining([
|
||||
{ id: 'cat1' },
|
||||
{ id: 'cat3' },
|
||||
{ id: 'cat5' },
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('categories already saved', () => {
|
||||
beforeEach(async () => {
|
||||
variables = {
|
||||
activeCategories: ['cat1', 'cat3', 'cat5'],
|
||||
}
|
||||
await mutate({ mutation: saveCategorySettings, variables })
|
||||
variables = {
|
||||
activeCategories: ['cat10', 'cat11', 'cat12', 'cat8', 'cat9'],
|
||||
}
|
||||
})
|
||||
|
||||
it('returns true', async () => {
|
||||
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { saveCategorySettings: true },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('query for user', () => {
|
||||
beforeEach(async () => {
|
||||
await mutate({ mutation: saveCategorySettings, variables })
|
||||
})
|
||||
|
||||
it('returns the new active categories when user is queried', async () => {
|
||||
await expect(
|
||||
query({ query: userQuery, variables: { id: authenticatedUser.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
User: [
|
||||
{
|
||||
activeCategories: expect.arrayContaining([
|
||||
{ id: 'cat10' },
|
||||
{ id: 'cat11' },
|
||||
{ id: 'cat12' },
|
||||
{ id: 'cat8' },
|
||||
{ id: 'cat9' },
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -115,6 +115,14 @@ type User {
|
||||
|
||||
emotions: [EMOTED]
|
||||
|
||||
activeCategories: [Category] @cypher(
|
||||
statement: """
|
||||
MATCH (category:Category)
|
||||
WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category))
|
||||
RETURN category
|
||||
"""
|
||||
)
|
||||
|
||||
myRoleInGroup: GroupMemberRole
|
||||
}
|
||||
|
||||
@ -222,4 +230,6 @@ type Mutation {
|
||||
unblockUser(id: ID!): User
|
||||
|
||||
switchUserRole(role: UserRole!, id: ID!): User
|
||||
|
||||
saveCategorySettings(activeCategories: [String]): Boolean
|
||||
}
|
||||
|
||||
58
webapp/components/FilterMenu/CategoriesMenu.vue
Normal file
58
webapp/components/FilterMenu/CategoriesMenu.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<dropdown ref="category-menu" placement="top-start" :offset="8" class="category-menu">
|
||||
<base-button
|
||||
slot="default"
|
||||
:filled="filterActive"
|
||||
:ghost="!filterActive"
|
||||
slot-scope="{ toggleMenu }"
|
||||
@click.prevent="toggleMenu()"
|
||||
>
|
||||
<ds-text uppercase>{{ $t('admin.categories.name') }}</ds-text>
|
||||
</base-button>
|
||||
<template slot="popover">
|
||||
<div class="category-menu-options">
|
||||
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
|
||||
<categories-filter v-if="categoriesActive" />
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import { mapGetters } from 'vuex'
|
||||
import CategoriesFilter from './CategoriesFilter'
|
||||
|
||||
export default {
|
||||
name: 'CategoriesMenu',
|
||||
components: {
|
||||
Dropdown,
|
||||
CategoriesFilter,
|
||||
},
|
||||
props: {
|
||||
placement: { type: String },
|
||||
offset: { type: [String, Number] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
filterActive: 'posts/isActive',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.category-menu-options {
|
||||
max-width: $size-max-width-filter-menu;
|
||||
padding: $space-small $space-x-small;
|
||||
|
||||
> .title {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -14,7 +14,6 @@
|
||||
<div class="filter-menu-options">
|
||||
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
|
||||
<following-filter />
|
||||
<categories-filter v-if="categoriesActive" />
|
||||
</div>
|
||||
<div class="filter-menu-options">
|
||||
<h2 class="title">{{ $t('filter-menu.order-by') }}</h2>
|
||||
@ -29,24 +28,17 @@ import Dropdown from '~/components/Dropdown'
|
||||
import { mapGetters } from 'vuex'
|
||||
import FollowingFilter from './FollowingFilter'
|
||||
import OrderByFilter from './OrderByFilter'
|
||||
import CategoriesFilter from './CategoriesFilter'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dropdown,
|
||||
FollowingFilter,
|
||||
CategoriesFilter,
|
||||
OrderByFilter,
|
||||
},
|
||||
props: {
|
||||
placement: { type: String },
|
||||
offset: { type: [String, Number] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
filterActive: 'posts/isActive',
|
||||
|
||||
@ -15,6 +15,15 @@
|
||||
>
|
||||
<base-button icon="bars" @click="toggleMobileMenuView" circle />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
v-if="categoriesActive && isLoggedIn"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
style="flex-grow: 0; flex-basis: auto"
|
||||
>
|
||||
<client-only>
|
||||
<categories-menu></categories-menu>
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '45%', sm: '45%', md: '45%', lg: '50%' }"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
@ -90,6 +99,7 @@ import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||
import PageFooter from '~/components/PageFooter/PageFooter'
|
||||
import AvatarMenu from '~/components/AvatarMenu/AvatarMenu'
|
||||
import InviteButton from '~/components/InviteButton/InviteButton'
|
||||
import CategoriesMenu from '~/components/FilterMenu/CategoriesMenu.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -102,6 +112,7 @@ export default {
|
||||
FilterMenu,
|
||||
PageFooter,
|
||||
InviteButton,
|
||||
CategoriesMenu,
|
||||
},
|
||||
mixins: [seo],
|
||||
data() {
|
||||
@ -109,6 +120,7 @@ export default {
|
||||
mobileSearchVisible: false,
|
||||
toggleMobileMenu: false,
|
||||
inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling,
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"admin": {
|
||||
"categories": {
|
||||
"categoryName": "Name",
|
||||
"name": "Kategorien",
|
||||
"name": "Themen",
|
||||
"postCount": "Beiträge"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -98,7 +98,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"category": "Kategorie ::: Kategorien",
|
||||
"category": "Thema ::: Themen",
|
||||
"comment": "Kommentar ::: Kommentare",
|
||||
"letsTalk": "Miteinander reden",
|
||||
"loading": "wird geladen",
|
||||
@ -113,7 +113,7 @@
|
||||
"takeAction": "Aktiv werden",
|
||||
"user": "Benutzer ::: Benutzer",
|
||||
"validations": {
|
||||
"categories": "es müssen eine bis drei Kategorien ausgewählt werden",
|
||||
"categories": "es müssen eine bis drei Themen ausgewählt werden",
|
||||
"email": "muss eine gültige E-Mail-Adresse sein",
|
||||
"url": "muss eine gültige URL sein"
|
||||
},
|
||||
@ -214,7 +214,7 @@
|
||||
"amount-shouts": "{amount} recommendations",
|
||||
"amount-views": "{amount} views",
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Kategorien ausgewählt"
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Themen ausgewählt"
|
||||
},
|
||||
"category": {
|
||||
"name": {
|
||||
@ -349,7 +349,7 @@
|
||||
},
|
||||
"filter-menu": {
|
||||
"all": "Alle",
|
||||
"categories": "Themenkategorien",
|
||||
"categories": "Themen",
|
||||
"emotions": "Emotionen",
|
||||
"filter-by": "Filtern nach ...",
|
||||
"following": "Benutzern, denen ich folge",
|
||||
@ -471,7 +471,7 @@
|
||||
"noDecision": "Keine Entscheidung!",
|
||||
"numberOfUsers": "{count} Nutzern",
|
||||
"previousDecision": "Vorherige Entscheidung:",
|
||||
"reasonCategory": "Kategorie",
|
||||
"reasonCategory": "Thema",
|
||||
"reasonDescription": "Beschreibung",
|
||||
"reportedOn": "Datum",
|
||||
"status": "Aktueller Status",
|
||||
@ -593,8 +593,8 @@
|
||||
},
|
||||
"reason": {
|
||||
"category": {
|
||||
"invalid": "Bitte wähle eine gültige Kategorie aus",
|
||||
"label": "Wähle eine Kategorie:",
|
||||
"invalid": "Bitte wähle ein gültiges Thema aus",
|
||||
"label": "Wähle ein Thema:",
|
||||
"options": {
|
||||
"advert_products_services_commercial": "Bewerben von Produkten und Dienstleistungen mit kommerzieller Absicht.",
|
||||
"criminal_behavior_violation_german_law": "Strafbares Verhalten bzw. Verstoß gegen deutsches Recht.",
|
||||
@ -605,7 +605,7 @@
|
||||
"other": "Andere …",
|
||||
"pornographic_content_links": "Das Senden oder Verlinken eindeutig pornografischen Materials."
|
||||
},
|
||||
"placeholder": "Kategorie …"
|
||||
"placeholder": "Thema …"
|
||||
},
|
||||
"description": {
|
||||
"label": "Bitte erkläre: Warum möchtest Du dies melden?",
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"admin": {
|
||||
"categories": {
|
||||
"categoryName": "Name",
|
||||
"name": "Categories",
|
||||
"name": "Topics",
|
||||
"postCount": "Posts"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -98,7 +98,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"category": "Category ::: Categories",
|
||||
"category": "Topic ::: Topics",
|
||||
"comment": "Comment ::: Comments",
|
||||
"letsTalk": "Let`s Talk",
|
||||
"loading": "loading",
|
||||
@ -113,7 +113,7 @@
|
||||
"takeAction": "Take Action",
|
||||
"user": "User ::: Users",
|
||||
"validations": {
|
||||
"categories": "at least one and at most three categories must be selected",
|
||||
"categories": "at least one and at most three topics must be selected",
|
||||
"email": "must be a valid e-mail address",
|
||||
"url": "must be a valid URL"
|
||||
},
|
||||
@ -214,7 +214,7 @@
|
||||
"amount-shouts": "{amount} recommendations",
|
||||
"amount-views": "{amount} views",
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected"
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} of {max} topics selected"
|
||||
},
|
||||
"category": {
|
||||
"name": {
|
||||
@ -349,7 +349,7 @@
|
||||
},
|
||||
"filter-menu": {
|
||||
"all": "All",
|
||||
"categories": "Categories of Content",
|
||||
"categories": "Topics",
|
||||
"emotions": "Emotions",
|
||||
"filter-by": "Filter by ...",
|
||||
"following": "Users I follow",
|
||||
@ -471,7 +471,7 @@
|
||||
"noDecision": "No decision!",
|
||||
"numberOfUsers": "{count} users",
|
||||
"previousDecision": "Previous decision:",
|
||||
"reasonCategory": "Category",
|
||||
"reasonCategory": "Topic",
|
||||
"reasonDescription": "Description",
|
||||
"reportedOn": "Date",
|
||||
"status": "Current status",
|
||||
@ -593,8 +593,8 @@
|
||||
},
|
||||
"reason": {
|
||||
"category": {
|
||||
"invalid": "Please select a valid category",
|
||||
"label": "Select a category:",
|
||||
"invalid": "Please select a valid topic",
|
||||
"label": "Select a topic:",
|
||||
"options": {
|
||||
"advert_products_services_commercial": "Advertising products and services with commercial intent.",
|
||||
"criminal_behavior_violation_german_law": "Criminal behavior or violation of German law.",
|
||||
@ -605,7 +605,7 @@
|
||||
"other": "Other …",
|
||||
"pornographic_content_links": "Posting or linking of clearly pornographic material."
|
||||
},
|
||||
"placeholder": "Category …"
|
||||
"placeholder": "Topic …"
|
||||
},
|
||||
"description": {
|
||||
"label": "Please explain: Why you like to report this?",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user