Merge branch 'master' of https://github.com/Human-Connection/Human-Connection into 1017-send-out-notifications-on-create-omment
@ -4,7 +4,6 @@ NEO4J_PASSWORD=letmein
|
||||
GRAPHQL_PORT=4000
|
||||
GRAPHQL_URI=http://localhost:4000
|
||||
CLIENT_URI=http://localhost:3000
|
||||
MOCKS=false
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_IGNORE_TLS=true
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:12.7-alpine as base
|
||||
FROM node:12.8-alpine as base
|
||||
LABEL Description="Backend of the Social Network Human-Connection.org" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
@ -44,8 +44,8 @@
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^15.1.0",
|
||||
"activitystrea.ms": "~2.1.3",
|
||||
"apollo-cache-inmemory": "~1.6.2",
|
||||
"apollo-client": "~2.6.3",
|
||||
"apollo-cache-inmemory": "~1.6.3",
|
||||
"apollo-client": "~2.6.4",
|
||||
"apollo-link-context": "~1.0.18",
|
||||
"apollo-link-http": "~1.5.15",
|
||||
"apollo-server": "~2.8.1",
|
||||
@ -118,7 +118,7 @@
|
||||
"eslint-config-prettier": "~6.0.0",
|
||||
"eslint-config-standard": "~13.0.1",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
"eslint-plugin-jest": "~22.15.0",
|
||||
"eslint-plugin-jest": "~22.15.1",
|
||||
"eslint-plugin-node": "~9.1.0",
|
||||
"eslint-plugin-prettier": "~3.1.0",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
|
||||
@ -32,7 +32,6 @@ export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI }
|
||||
|
||||
export const developmentConfigs = {
|
||||
DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true',
|
||||
MOCKS: process.env.MOCKS === 'true',
|
||||
DISABLED_MIDDLEWARES:
|
||||
(process.env.NODE_ENV !== 'production' && process.env.DISABLED_MIDDLEWARES) || '',
|
||||
}
|
||||
|
||||
@ -157,6 +157,8 @@ const permissions = shield(
|
||||
User: or(noEmailFilter, isAdmin),
|
||||
isLoggedIn: allow,
|
||||
Badge: allow,
|
||||
PostsEmotionsCountByEmotion: allow,
|
||||
PostsEmotionsByCurrentUser: allow,
|
||||
},
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
@ -178,7 +180,6 @@ const permissions = shield(
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
unreward: isAdmin,
|
||||
// addFruitToBasket: isAuthenticated
|
||||
follow: isAuthenticated,
|
||||
unfollow: isAuthenticated,
|
||||
shout: isAuthenticated,
|
||||
@ -192,6 +193,8 @@ const permissions = shield(
|
||||
DeleteUser: isDeletingOwnAccount,
|
||||
requestPasswordReset: allow,
|
||||
resetPassword: allow,
|
||||
AddPostEmotions: isAuthenticated,
|
||||
RemovePostEmotions: isAuthenticated,
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import faker from 'faker'
|
||||
|
||||
export default {
|
||||
User: () => ({
|
||||
name: () => `${faker.name.firstName()} ${faker.name.lastName()}`,
|
||||
email: () => `${faker.internet.email()}`,
|
||||
}),
|
||||
Post: () => ({
|
||||
title: () => faker.lorem.lines(1),
|
||||
slug: () => faker.lorem.slug(3),
|
||||
content: () => faker.lorem.paragraphs(5),
|
||||
contentExcerpt: () => faker.lorem.paragraphs(1),
|
||||
}),
|
||||
}
|
||||
34
backend/src/models/Post.js
Normal file
@ -0,0 +1,34 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
module.exports = {
|
||||
id: { type: 'string', primary: true, default: uuid },
|
||||
activityId: { type: 'string', allow: [null] },
|
||||
objectId: { type: 'string', allow: [null] },
|
||||
author: {
|
||||
type: 'relationship',
|
||||
relationship: 'WROTE',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
},
|
||||
title: { type: 'string', disallow: [null], min: 3 },
|
||||
slug: { type: 'string', allow: [null] },
|
||||
content: { type: 'string', disallow: [null], min: 3 },
|
||||
contentExcerpt: { type: 'string', allow: [null] },
|
||||
image: { type: 'string', allow: [null] },
|
||||
deleted: { type: 'boolean', default: false },
|
||||
disabled: { type: 'boolean', default: false },
|
||||
disabledBy: {
|
||||
type: 'relationship',
|
||||
relationship: 'DISABLED',
|
||||
target: 'User',
|
||||
direction: 'in',
|
||||
},
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
isoDate: true,
|
||||
required: true,
|
||||
default: () => new Date().toISOString(),
|
||||
},
|
||||
language: { type: 'string', allow: [null] },
|
||||
}
|
||||
@ -56,4 +56,19 @@ module.exports = {
|
||||
required: true,
|
||||
default: () => new Date().toISOString(),
|
||||
},
|
||||
emoted: {
|
||||
type: 'relationships',
|
||||
relationship: 'EMOTED',
|
||||
target: 'Post',
|
||||
direction: 'out',
|
||||
properties: {
|
||||
emotion: {
|
||||
type: 'string',
|
||||
valid: ['happy', 'cry', 'surprised', 'angry', 'funny'],
|
||||
invalid: [null],
|
||||
},
|
||||
},
|
||||
eager: true,
|
||||
cascade: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -6,4 +6,5 @@ export default {
|
||||
InvitationCode: require('./InvitationCode.js'),
|
||||
EmailAddress: require('./EmailAddress.js'),
|
||||
SocialMedia: require('./SocialMedia.js'),
|
||||
Post: require('./Post.js'),
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ export default {
|
||||
delete params.categoryIds
|
||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||
params.id = params.id || uuid()
|
||||
|
||||
let createPostCypher = `CREATE (post:Post {params})
|
||||
WITH post
|
||||
MATCH (author:User {id: $userId})
|
||||
@ -70,5 +71,80 @@ export default {
|
||||
|
||||
return post.properties
|
||||
},
|
||||
AddPostEmotions: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { to, data } = params
|
||||
const { user } = context
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (userFrom:User {id: $user.id}), (postTo:Post {id: $to.id})
|
||||
MERGE (userFrom)-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo)
|
||||
RETURN userFrom, postTo, emotedRelation`,
|
||||
{ user, to, data },
|
||||
)
|
||||
session.close()
|
||||
const [emoted] = transactionRes.records.map(record => {
|
||||
return {
|
||||
from: { ...record.get('userFrom').properties },
|
||||
to: { ...record.get('postTo').properties },
|
||||
...record.get('emotedRelation').properties,
|
||||
}
|
||||
})
|
||||
return emoted
|
||||
},
|
||||
RemovePostEmotions: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { to, data } = params
|
||||
const { id: from } = context.user
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (userFrom:User {id: $from})-[emotedRelation:EMOTED {emotion: $data.emotion}]->(postTo:Post {id: $to.id})
|
||||
DELETE emotedRelation
|
||||
RETURN userFrom, postTo`,
|
||||
{ from, to, data },
|
||||
)
|
||||
session.close()
|
||||
const [emoted] = transactionRes.records.map(record => {
|
||||
return {
|
||||
from: { ...record.get('userFrom').properties },
|
||||
to: { ...record.get('postTo').properties },
|
||||
emotion: data.emotion,
|
||||
}
|
||||
})
|
||||
return emoted
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { postId, data } = params
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
||||
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
||||
`,
|
||||
{ postId, data },
|
||||
)
|
||||
session.close()
|
||||
|
||||
const [emotionsCount] = transactionRes.records.map(record => {
|
||||
return record.get('emotionsCount').low
|
||||
})
|
||||
|
||||
return emotionsCount
|
||||
},
|
||||
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { postId } = params
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
||||
RETURN collect(emoted.emotion) as emotion`,
|
||||
{ userId: context.user.id, postId },
|
||||
)
|
||||
|
||||
session.close()
|
||||
|
||||
const [emotions] = transactionRes.records.map(record => {
|
||||
return record.get('emotion')
|
||||
})
|
||||
return emotions
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { host, login } from '../../jest/helpers'
|
||||
import { host, login, gql } from '../../jest/helpers'
|
||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
|
||||
let client
|
||||
let userParams
|
||||
let authorParams
|
||||
@ -14,7 +20,7 @@ const oldContent = 'Old content'
|
||||
const newTitle = 'New title'
|
||||
const newContent = 'New content'
|
||||
const createPostVariables = { title: postTitle, content: postContent }
|
||||
const createPostWithCategoriesMutation = `
|
||||
const createPostWithCategoriesMutation = gql`
|
||||
mutation($title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
@ -27,7 +33,7 @@ const createPostWithCategoriesVariables = {
|
||||
content: postContent,
|
||||
categoryIds: ['cat9', 'cat4', 'cat15'],
|
||||
}
|
||||
const postQueryWithCategories = `
|
||||
const postQueryWithCategories = gql`
|
||||
query($id: ID) {
|
||||
Post(id: $id) {
|
||||
categories {
|
||||
@ -41,9 +47,9 @@ const createPostWithoutCategoriesVariables = {
|
||||
content: 'I should be able to filter it out',
|
||||
categoryIds: null,
|
||||
}
|
||||
const postQueryFilteredByCategory = `
|
||||
query Post($filter: _PostFilter) {
|
||||
Post(filter: $filter) {
|
||||
const postQueryFilteredByCategory = gql`
|
||||
query Post($filter: _PostFilter) {
|
||||
Post(filter: $filter) {
|
||||
title
|
||||
id
|
||||
categories {
|
||||
@ -56,13 +62,28 @@ const postCategoriesFilterParam = { categories_some: { id_in: ['cat4'] } }
|
||||
const postQueryFilteredByCategoryVariables = {
|
||||
filter: postCategoriesFilterParam,
|
||||
}
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
content
|
||||
slug
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
userParams = {
|
||||
id: 'u198',
|
||||
name: 'TestUser',
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
}
|
||||
authorParams = {
|
||||
id: 'u25',
|
||||
email: 'author@example.org',
|
||||
password: '1234',
|
||||
}
|
||||
@ -74,22 +95,12 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('CreatePost', () => {
|
||||
const mutation = `
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
title
|
||||
content
|
||||
slug
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation, createPostVariables)).rejects.toThrow('Not Authorised')
|
||||
await expect(client.request(createPostMutation, createPostVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -107,19 +118,23 @@ describe('CreatePost', () => {
|
||||
content: postContent,
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation, createPostVariables)).resolves.toMatchObject(expected)
|
||||
await expect(client.request(createPostMutation, createPostVariables)).resolves.toMatchObject(
|
||||
expected,
|
||||
)
|
||||
})
|
||||
|
||||
it('assigns the authenticated user as author', async () => {
|
||||
await client.request(mutation, createPostVariables)
|
||||
await client.request(createPostMutation, createPostVariables)
|
||||
const { User } = await client.request(
|
||||
`{
|
||||
User(name: "TestUser") {
|
||||
contributions {
|
||||
title
|
||||
gql`
|
||||
{
|
||||
User(name: "TestUser") {
|
||||
contributions {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`,
|
||||
{ headers },
|
||||
)
|
||||
expect(User).toEqual([{ contributions: [{ title: postTitle }] }])
|
||||
@ -128,13 +143,15 @@ describe('CreatePost', () => {
|
||||
describe('disabled and deleted', () => {
|
||||
it('initially false', async () => {
|
||||
const expected = { CreatePost: { disabled: false, deleted: false } }
|
||||
await expect(client.request(mutation, createPostVariables)).resolves.toMatchObject(expected)
|
||||
await expect(
|
||||
client.request(createPostMutation, createPostVariables),
|
||||
).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('language', () => {
|
||||
it('allows a user to set the language of the post', async () => {
|
||||
const createPostWithLanguageMutation = `
|
||||
const createPostWithLanguageMutation = gql`
|
||||
mutation($title: String!, $content: String!, $language: String) {
|
||||
CreatePost(title: $title, content: $content, language: $language) {
|
||||
language
|
||||
@ -222,7 +239,7 @@ describe('UpdatePost', () => {
|
||||
title: oldTitle,
|
||||
content: oldContent,
|
||||
})
|
||||
updatePostMutation = `
|
||||
updatePostMutation = gql`
|
||||
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
@ -328,7 +345,7 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
describe('DeletePost', () => {
|
||||
const mutation = `
|
||||
const mutation = gql`
|
||||
mutation($id: ID!) {
|
||||
DeletePost(id: $id) {
|
||||
id
|
||||
@ -383,3 +400,315 @@ describe('DeletePost', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('emotions', () => {
|
||||
let addPostEmotionsVariables,
|
||||
someUser,
|
||||
ownerNode,
|
||||
owner,
|
||||
postMutationAction,
|
||||
user,
|
||||
postQueryAction,
|
||||
postToEmote,
|
||||
postToEmoteNode
|
||||
const PostsEmotionsCountQuery = `
|
||||
query($id: ID!) {
|
||||
Post(id: $id) {
|
||||
emotionsCount
|
||||
}
|
||||
}
|
||||
`
|
||||
const PostsEmotionsQuery = gql`
|
||||
query($id: ID!) {
|
||||
Post(id: $id) {
|
||||
emotions {
|
||||
emotion
|
||||
User {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const addPostEmotionsMutation = gql`
|
||||
mutation($to: _PostInput!, $data: _EMOTEDInput!) {
|
||||
AddPostEmotions(to: $to, data: $data) {
|
||||
from {
|
||||
id
|
||||
}
|
||||
to {
|
||||
id
|
||||
}
|
||||
emotion
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
userParams.id = 'u1987'
|
||||
authorParams.id = 'u257'
|
||||
createPostVariables.id = 'p1376'
|
||||
const someUserNode = await instance.create('User', userParams)
|
||||
someUser = await someUserNode.toJson()
|
||||
ownerNode = await instance.create('User', authorParams)
|
||||
owner = await ownerNode.toJson()
|
||||
postToEmoteNode = await instance.create('Post', createPostVariables)
|
||||
postToEmote = await postToEmoteNode.toJson()
|
||||
await postToEmoteNode.relateTo(ownerNode, 'author')
|
||||
|
||||
postMutationAction = async (user, mutation, variables) => {
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
})
|
||||
const { mutate } = createTestClient(server)
|
||||
|
||||
return mutate({
|
||||
mutation,
|
||||
variables,
|
||||
})
|
||||
}
|
||||
postQueryAction = async (postQuery, variables) => {
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
return query({ query: postQuery, variables })
|
||||
}
|
||||
addPostEmotionsVariables = {
|
||||
to: { id: postToEmote.id },
|
||||
data: { emotion: 'happy' },
|
||||
}
|
||||
})
|
||||
|
||||
describe('AddPostEmotions', () => {
|
||||
let postsEmotionsQueryVariables
|
||||
beforeEach(async () => {
|
||||
postsEmotionsQueryVariables = { id: postToEmote.id }
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
user = null
|
||||
const addPostEmotions = await postMutationAction(
|
||||
user,
|
||||
addPostEmotionsMutation,
|
||||
addPostEmotionsVariables,
|
||||
)
|
||||
|
||||
expect(addPostEmotions.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated and not the author', () => {
|
||||
beforeEach(() => {
|
||||
user = someUser
|
||||
})
|
||||
|
||||
it('adds an emotion to the post', async () => {
|
||||
const expected = {
|
||||
data: {
|
||||
AddPostEmotions: {
|
||||
from: { id: user.id },
|
||||
to: addPostEmotionsVariables.to,
|
||||
emotion: 'happy',
|
||||
},
|
||||
},
|
||||
}
|
||||
await expect(
|
||||
postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expected))
|
||||
})
|
||||
|
||||
it('limits the addition of the same emotion to 1', async () => {
|
||||
const expected = {
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
emotionsCount: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
await postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables)
|
||||
await postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables)
|
||||
await expect(
|
||||
postQueryAction(PostsEmotionsCountQuery, postsEmotionsQueryVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expected))
|
||||
})
|
||||
|
||||
it('allows a user to add more than one emotion', async () => {
|
||||
const expectedEmotions = [
|
||||
{ emotion: 'happy', User: { id: user.id } },
|
||||
{ emotion: 'surprised', User: { id: user.id } },
|
||||
]
|
||||
const expectedResponse = {
|
||||
data: { Post: [{ emotions: expect.arrayContaining(expectedEmotions) }] },
|
||||
}
|
||||
await postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables)
|
||||
addPostEmotionsVariables.data.emotion = 'surprised'
|
||||
await postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables)
|
||||
await expect(
|
||||
postQueryAction(PostsEmotionsQuery, postsEmotionsQueryVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expectedResponse))
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as author', () => {
|
||||
beforeEach(() => {
|
||||
user = owner
|
||||
})
|
||||
|
||||
it('adds an emotion to the post', async () => {
|
||||
const expected = {
|
||||
data: {
|
||||
AddPostEmotions: {
|
||||
from: { id: owner.id },
|
||||
to: addPostEmotionsVariables.to,
|
||||
emotion: 'happy',
|
||||
},
|
||||
},
|
||||
}
|
||||
await expect(
|
||||
postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expected))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('RemovePostEmotions', () => {
|
||||
let removePostEmotionsVariables, postsEmotionsQueryVariables
|
||||
const removePostEmotionsMutation = gql`
|
||||
mutation($to: _PostInput!, $data: _EMOTEDInput!) {
|
||||
RemovePostEmotions(to: $to, data: $data) {
|
||||
from {
|
||||
id
|
||||
}
|
||||
to {
|
||||
id
|
||||
}
|
||||
emotion
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
await ownerNode.relateTo(postToEmoteNode, 'emoted', { emotion: 'cry' })
|
||||
await postMutationAction(user, addPostEmotionsMutation, addPostEmotionsVariables)
|
||||
|
||||
postsEmotionsQueryVariables = { id: postToEmote.id }
|
||||
removePostEmotionsVariables = {
|
||||
to: { id: postToEmote.id },
|
||||
data: { emotion: 'cry' },
|
||||
}
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
user = null
|
||||
const removePostEmotions = await postMutationAction(
|
||||
user,
|
||||
removePostEmotionsMutation,
|
||||
removePostEmotionsVariables,
|
||||
)
|
||||
expect(removePostEmotions.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
describe('but not the emoter', () => {
|
||||
it('returns null if the emotion could not be found', async () => {
|
||||
user = someUser
|
||||
const removePostEmotions = await postMutationAction(
|
||||
user,
|
||||
removePostEmotionsMutation,
|
||||
removePostEmotionsVariables,
|
||||
)
|
||||
expect(removePostEmotions).toEqual(
|
||||
expect.objectContaining({ data: { RemovePostEmotions: null } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('as the emoter', () => {
|
||||
it('removes an emotion from a post', async () => {
|
||||
user = owner
|
||||
const expected = {
|
||||
data: {
|
||||
RemovePostEmotions: {
|
||||
to: { id: postToEmote.id },
|
||||
from: { id: user.id },
|
||||
emotion: 'cry',
|
||||
},
|
||||
},
|
||||
}
|
||||
await expect(
|
||||
postMutationAction(user, removePostEmotionsMutation, removePostEmotionsVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expected))
|
||||
})
|
||||
|
||||
it('removes only the requested emotion, not all emotions', async () => {
|
||||
const expectedEmotions = [{ emotion: 'happy', User: { id: authorParams.id } }]
|
||||
const expectedResponse = {
|
||||
data: { Post: [{ emotions: expect.arrayContaining(expectedEmotions) }] },
|
||||
}
|
||||
await postMutationAction(user, removePostEmotionsMutation, removePostEmotionsVariables)
|
||||
await expect(
|
||||
postQueryAction(PostsEmotionsQuery, postsEmotionsQueryVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expectedResponse))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('posts emotions count', () => {
|
||||
let PostsEmotionsCountByEmotionVariables
|
||||
let PostsEmotionsByCurrentUserVariables
|
||||
|
||||
const PostsEmotionsCountByEmotionQuery = gql`
|
||||
query($postId: ID!, $data: _EMOTEDInput!) {
|
||||
PostsEmotionsCountByEmotion(postId: $postId, data: $data)
|
||||
}
|
||||
`
|
||||
|
||||
const PostsEmotionsByCurrentUserQuery = gql`
|
||||
query($postId: ID!) {
|
||||
PostsEmotionsByCurrentUser(postId: $postId)
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
await ownerNode.relateTo(postToEmoteNode, 'emoted', { emotion: 'cry' })
|
||||
|
||||
PostsEmotionsCountByEmotionVariables = {
|
||||
postId: postToEmote.id,
|
||||
data: { emotion: 'cry' },
|
||||
}
|
||||
PostsEmotionsByCurrentUserVariables = { postId: postToEmote.id }
|
||||
})
|
||||
|
||||
describe('PostsEmotionsCountByEmotion', () => {
|
||||
it("returns a post's emotions count", async () => {
|
||||
const expectedResponse = { data: { PostsEmotionsCountByEmotion: 1 } }
|
||||
await expect(
|
||||
postQueryAction(PostsEmotionsCountByEmotionQuery, PostsEmotionsCountByEmotionVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expectedResponse))
|
||||
})
|
||||
})
|
||||
|
||||
describe('PostsEmotionsCountByEmotion', () => {
|
||||
it("returns a currentUser's emotions on a post", async () => {
|
||||
const expectedResponse = { data: { PostsEmotionsByCurrentUser: ['cry'] } }
|
||||
await expect(
|
||||
postQueryAction(PostsEmotionsByCurrentUserQuery, PostsEmotionsByCurrentUserVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expectedResponse))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -7,4 +7,4 @@ type EMOTED @relation(name: "EMOTED") {
|
||||
#updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +50,8 @@ type Post {
|
||||
)
|
||||
|
||||
emotions: [EMOTED]
|
||||
emotionsCount: Int!
|
||||
@cypher(statement: "MATCH (this)<-[emoted:EMOTED]-(:User) RETURN COUNT(DISTINCT emoted)")
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@ -89,4 +91,11 @@ type Mutation {
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
): Post
|
||||
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||
RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
|
||||
}
|
||||
|
||||
type Query {
|
||||
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
||||
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
||||
}
|
||||
|
||||
@ -128,6 +128,22 @@ export default function Factory(options = {}) {
|
||||
this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver })
|
||||
return this
|
||||
},
|
||||
async emote({ to, data }) {
|
||||
const mutation = `
|
||||
mutation {
|
||||
AddPostEmotions(
|
||||
to: { id: "${to}" },
|
||||
data: { emotion: ${data} }
|
||||
) {
|
||||
from { id }
|
||||
to { id }
|
||||
emotion
|
||||
}
|
||||
}
|
||||
`
|
||||
this.lastResponse = await this.graphQLClient.request(mutation)
|
||||
return this
|
||||
},
|
||||
}
|
||||
result.authenticateAs.bind(result)
|
||||
result.create.bind(result)
|
||||
|
||||
@ -503,6 +503,116 @@ import Factory from './factories'
|
||||
from: 'p15',
|
||||
to: 'Demokratie',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u1',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u2',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u3',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u4',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u5',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u6',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u7',
|
||||
to: 'p15',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u2',
|
||||
to: 'p14',
|
||||
data: 'cry',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u3',
|
||||
to: 'p13',
|
||||
data: 'angry',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u4',
|
||||
to: 'p12',
|
||||
data: 'funny',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u5',
|
||||
to: 'p11',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u6',
|
||||
to: 'p10',
|
||||
data: 'cry',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u5',
|
||||
to: 'p9',
|
||||
data: 'happy',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u4',
|
||||
to: 'p8',
|
||||
data: 'angry',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u3',
|
||||
to: 'p7',
|
||||
data: 'funny',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u2',
|
||||
to: 'p6',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u1',
|
||||
to: 'p5',
|
||||
data: 'cry',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u2',
|
||||
to: 'p4',
|
||||
data: 'happy',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u3',
|
||||
to: 'p3',
|
||||
data: 'angry',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u4',
|
||||
to: 'p2',
|
||||
data: 'funny',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u5',
|
||||
to: 'p1',
|
||||
data: 'surprised',
|
||||
}),
|
||||
f.emote({
|
||||
from: 'u6',
|
||||
to: 'p0',
|
||||
data: 'cry',
|
||||
}),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
|
||||
@ -2,7 +2,6 @@ import express from 'express'
|
||||
import helmet from 'helmet'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import CONFIG, { requiredConfigs } from './config'
|
||||
import mocks from './mocks'
|
||||
import middleware from './middleware'
|
||||
import { getDriver } from './bootstrap/neo4j'
|
||||
import decode from './jwt/decode'
|
||||
@ -34,7 +33,6 @@ const createServer = options => {
|
||||
schema: middleware(schema),
|
||||
debug: CONFIG.DEBUG,
|
||||
tracing: CONFIG.DEBUG,
|
||||
mocks: CONFIG.MOCKS ? mocks : false,
|
||||
}
|
||||
const server = new ApolloServer(Object.assign({}, defaults, options))
|
||||
|
||||
|
||||
@ -1493,14 +1493,14 @@ apollo-cache-control@0.8.1:
|
||||
apollo-server-env "2.4.1"
|
||||
graphql-extensions "0.8.1"
|
||||
|
||||
apollo-cache-inmemory@~1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.2.tgz#bbf2e4e1eacdf82b2d526f5c2f3b37e5acee3c5e"
|
||||
integrity sha512-AyCl3PGFv5Qv1w4N9vlg63GBPHXgMCekZy5mhlS042ji0GW84uTySX+r3F61ZX3+KM1vA4m9hQyctrEGiv5XjQ==
|
||||
apollo-cache-inmemory@~1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d"
|
||||
integrity sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg==
|
||||
dependencies:
|
||||
apollo-cache "^1.3.2"
|
||||
apollo-utilities "^1.3.2"
|
||||
optimism "^0.9.0"
|
||||
optimism "^0.10.0"
|
||||
ts-invariant "^0.4.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
@ -1512,10 +1512,10 @@ apollo-cache@1.3.2, apollo-cache@^1.3.2:
|
||||
apollo-utilities "^1.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-client@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.3.tgz#9bb2d42fb59f1572e51417f341c5f743798d22db"
|
||||
integrity sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==
|
||||
apollo-client@~2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.4.tgz#872c32927263a0d34655c5ef8a8949fbb20b6140"
|
||||
integrity sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ==
|
||||
dependencies:
|
||||
"@types/zen-observable" "^0.8.0"
|
||||
apollo-cache "1.3.2"
|
||||
@ -3292,10 +3292,10 @@ eslint-plugin-import@~2.18.2:
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jest@~22.15.0:
|
||||
version "22.15.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.15.0.tgz#fe70bfff7eeb47ca0ab229588a867f82bb8592c5"
|
||||
integrity sha512-hgnPbSqAIcLLS9ePb12hNHTRkXnkVaCfOwCt2pzQ8KpOKPWGA4HhLMaFN38NBa/0uvLfrZpcIRjT+6tMAfr58Q==
|
||||
eslint-plugin-jest@~22.15.1:
|
||||
version "22.15.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.15.1.tgz#54c4a752a44c4bc5a564ecc22b32e1cd16a2961a"
|
||||
integrity sha512-CWq/RR/3tLaKFB+FZcCJwU9hH5q/bKeO3rFP8G07+q7hcDCFNqpvdphVbEbGE6o6qo1UbciEev4ejUWv7brUhw==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^1.13.0"
|
||||
|
||||
@ -6478,10 +6478,10 @@ onetime@^2.0.0:
|
||||
dependencies:
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
optimism@^0.9.0:
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.9.5.tgz#b8b5dc9150e97b79ddbf2d2c6c0e44de4d255527"
|
||||
integrity sha512-lNvmuBgONAGrUbj/xpH69FjMOz1d0jvMNoOCKyVynUPzq2jgVlGL4jFYJqrUHzUfBv+jAFSCP61x5UkfbduYJA==
|
||||
optimism@^0.10.0:
|
||||
version "0.10.2"
|
||||
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.10.2.tgz#626b6fd28b0923de98ecb36a3fd2d3d4e5632dd9"
|
||||
integrity sha512-zPfBIxFFWMmQboM9+Z4MSJqc1PXp82v1PFq/GfQaufI69mHKlup7ykGNnfuGIGssXJQkmhSodQ/k9EWwjd8O8A==
|
||||
dependencies:
|
||||
"@wry/context" "^0.4.0"
|
||||
|
||||
|
||||
@ -29,9 +29,9 @@ Feature: Tags and Categories
|
||||
|
||||
Scenario: See an overview of tags
|
||||
When I navigate to the administration dashboard
|
||||
And I click on the menu item "Tags"
|
||||
And I click on the menu item "Hashtags"
|
||||
Then I can see the following table:
|
||||
| | Name | Users | Posts |
|
||||
| 1 | Democracy | 3 | 4 |
|
||||
| 2 | Nature | 2 | 3 |
|
||||
| 3 | Ecology | 1 | 1 |
|
||||
| No. | Hashtags | Users | Posts |
|
||||
| 1 | #Democracy | 3 | 4 |
|
||||
| 2 | #Nature | 2 | 3 |
|
||||
| 3 | #Ecology | 1 | 1 |
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
SMTP_PORT: "25"
|
||||
GRAPHQL_PORT: "4000"
|
||||
GRAPHQL_URI: "http://nitro-backend.human-connection:4000"
|
||||
MOCKS: "false"
|
||||
NEO4J_URI: "bolt://nitro-neo4j.human-connection:7687"
|
||||
NEO4J_AUTH: "none"
|
||||
CLIENT_URI: "https://nitro-staging.human-connection.org"
|
||||
|
||||
@ -19,7 +19,6 @@ services:
|
||||
- GRAPHQL_URI=http://localhost:4000
|
||||
- CLIENT_URI=http://localhost:3000
|
||||
- JWT_SECRET=b/&&7b78BF&fv/Vd
|
||||
- MOCKS=false
|
||||
- MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ
|
||||
- PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78
|
||||
- NEO4J_apoc_import_file_enabled=true
|
||||
|
||||
@ -33,7 +33,6 @@ services:
|
||||
- GRAPHQL_URI=http://localhost:4000
|
||||
- CLIENT_URI=http://localhost:3000
|
||||
- JWT_SECRET=b/&&7b78BF&fv/Vd
|
||||
- MOCKS=false
|
||||
- MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ
|
||||
- PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78
|
||||
neo4j:
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"codecov": "^3.5.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"cypress": "^3.4.1",
|
||||
"cypress-cucumber-preprocessor": "^1.13.0",
|
||||
"cypress-cucumber-preprocessor": "^1.13.1",
|
||||
"cypress-file-upload": "^3.3.3",
|
||||
"cypress-plugin-retries": "^1.2.2",
|
||||
"dotenv": "^8.0.0",
|
||||
@ -34,4 +34,4 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"slug": "^1.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:12.7-alpine as base
|
||||
FROM node:12.8-alpine as base
|
||||
LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 3000
|
||||
@ -18,7 +18,7 @@ COPY . .
|
||||
|
||||
FROM base as build-and-test
|
||||
RUN cp .env.template .env
|
||||
RUN yarn install --ignore-engines --production=false --frozen-lockfile --non-interactive
|
||||
RUN yarn install --production=false --frozen-lockfile --non-interactive
|
||||
RUN yarn run build
|
||||
|
||||
FROM base as production
|
||||
|
||||
127
webapp/components/Emotions/Emotions.spec.js
Normal file
@ -0,0 +1,127 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Emotions from './Emotions.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Vuex)
|
||||
|
||||
describe('Emotions.vue', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
let propsData
|
||||
let getters
|
||||
let funnyButton
|
||||
let funnyImage
|
||||
const funnyImageSrc = '/img/svg/emoji/funny_color.svg'
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
AddPostEmotions: {
|
||||
to: { id: 'p143' },
|
||||
data: { emotion: 'happy' },
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
RemovePostEmotions: {
|
||||
from: { id: 'u176' },
|
||||
to: { id: 'p143' },
|
||||
data: { emotion: 'happy' },
|
||||
},
|
||||
},
|
||||
}),
|
||||
query: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
PostsEmotionsCountByEmotion: 1,
|
||||
},
|
||||
}),
|
||||
},
|
||||
$t: jest.fn(),
|
||||
}
|
||||
propsData = {
|
||||
post: { id: 'p143' },
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return { id: 'u176' }
|
||||
},
|
||||
}
|
||||
})
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return mount(Emotions, { mocks, propsData, store, localVue })
|
||||
}
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it("queries the post's emotions count for each of the 5 emotions", () => {
|
||||
expect(mocks.$apollo.query).toHaveBeenCalledTimes(5)
|
||||
})
|
||||
|
||||
describe('adding emotions', () => {
|
||||
let expectedParams
|
||||
beforeEach(() => {
|
||||
wrapper.vm.PostsEmotionsCountByEmotion.funny = 0
|
||||
funnyButton = wrapper.findAll('button').at(0)
|
||||
funnyButton.trigger('click')
|
||||
})
|
||||
|
||||
it('shows the colored image when the button is active', () => {
|
||||
funnyImage = wrapper.findAll('img').at(0)
|
||||
expect(funnyImage.attributes().src).toEqual(funnyImageSrc)
|
||||
})
|
||||
|
||||
it('sends the AddPostEmotionsMutation for an emotion when clicked', () => {
|
||||
expectedParams = {
|
||||
mutation: PostMutations().AddPostEmotionsMutation,
|
||||
variables: { to: { id: 'p143' }, data: { emotion: 'funny' } },
|
||||
}
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('increases the PostsEmotionsCountByEmotion for the emotion clicked', () => {
|
||||
expect(wrapper.vm.PostsEmotionsCountByEmotion.funny).toEqual(1)
|
||||
})
|
||||
|
||||
it('adds an emotion to selectedEmotions to show the colored image when the button is active', () => {
|
||||
expect(wrapper.vm.selectedEmotions).toEqual(['funny'])
|
||||
})
|
||||
|
||||
describe('removing emotions', () => {
|
||||
beforeEach(() => {
|
||||
funnyButton.trigger('click')
|
||||
})
|
||||
|
||||
it('sends the RemovePostEmotionsMutation when a user clicks on an active emotion', () => {
|
||||
expectedParams = {
|
||||
mutation: PostMutations().RemovePostEmotionsMutation,
|
||||
variables: { to: { id: 'p143' }, data: { emotion: 'funny' } },
|
||||
}
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('decreases the PostsEmotionsCountByEmotion for the emotion clicked', async () => {
|
||||
expect(wrapper.vm.PostsEmotionsCountByEmotion.funny).toEqual(0)
|
||||
})
|
||||
|
||||
it('removes an emotion from selectedEmotions to show the default image', async () => {
|
||||
expect(wrapper.vm.selectedEmotions).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
115
webapp/components/Emotions/Emotions.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<ds-flex :gutter="{ lg: 'large' }" class="emotions-flex">
|
||||
<div v-for="emotion in Object.keys(PostsEmotionsCountByEmotion)" :key="emotion">
|
||||
<ds-flex-item :width="{ lg: '100%' }">
|
||||
<hc-emotions-button
|
||||
@toggleEmotion="toggleEmotion"
|
||||
:PostsEmotionsCountByEmotion="PostsEmotionsCountByEmotion"
|
||||
:iconPath="iconPath(emotion)"
|
||||
:emotion="emotion"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
</div>
|
||||
</ds-flex>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { mapGetters } from 'vuex'
|
||||
import HcEmotionsButton from '~/components/EmotionsButton/EmotionsButton'
|
||||
import { PostsEmotionsByCurrentUser } from '~/graphql/PostQuery.js'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEmotionsButton,
|
||||
},
|
||||
props: {
|
||||
post: { type: Object, default: () => {} },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedEmotions: [],
|
||||
PostsEmotionsCountByEmotion: { funny: 0, happy: 0, surprised: 0, cry: 0, angry: 0 },
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
},
|
||||
created() {
|
||||
Object.keys(this.PostsEmotionsCountByEmotion).map(emotion => {
|
||||
this.emotionsCount(emotion)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
iconPath(emotion) {
|
||||
if (this.isActive(emotion)) {
|
||||
return `/img/svg/emoji/${emotion}_color.svg`
|
||||
}
|
||||
return `/img/svg/emoji/${emotion}.svg`
|
||||
},
|
||||
toggleEmotion(emotion) {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: this.isActive(emotion)
|
||||
? PostMutations().RemovePostEmotionsMutation
|
||||
: PostMutations().AddPostEmotionsMutation,
|
||||
variables: {
|
||||
to: { id: this.post.id },
|
||||
data: { emotion },
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.isActive(emotion)
|
||||
? this.PostsEmotionsCountByEmotion[emotion]--
|
||||
: this.PostsEmotionsCountByEmotion[emotion]++
|
||||
|
||||
const index = this.selectedEmotions.indexOf(emotion)
|
||||
if (index > -1) {
|
||||
this.selectedEmotions.splice(index, 1)
|
||||
} else {
|
||||
this.selectedEmotions.push(emotion)
|
||||
}
|
||||
})
|
||||
},
|
||||
isActive(emotion) {
|
||||
const index = this.selectedEmotions.indexOf(emotion)
|
||||
if (index > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
emotionsCount(emotion) {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: gql`
|
||||
query($postId: ID!, $data: _EMOTEDInput!) {
|
||||
PostsEmotionsCountByEmotion(postId: $postId, data: $data)
|
||||
}
|
||||
`,
|
||||
variables: { postId: this.post.id, data: { emotion } },
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
.then(({ data: { PostsEmotionsCountByEmotion } }) => {
|
||||
this.PostsEmotionsCountByEmotion[emotion] = PostsEmotionsCountByEmotion
|
||||
})
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
PostsEmotionsByCurrentUser: {
|
||||
query() {
|
||||
return PostsEmotionsByCurrentUser()
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
postId: this.post.id,
|
||||
}
|
||||
},
|
||||
result({ data: { PostsEmotionsByCurrentUser } }) {
|
||||
this.selectedEmotions = PostsEmotionsByCurrentUser
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
50
webapp/components/EmotionsButton/EmotionsButton.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-button size="large" ghost @click="toggleEmotion(emotion)" class="emotions-buttons">
|
||||
<img :src="iconPath" width="40" />
|
||||
</ds-button>
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<div class="emotions-mobile-space">
|
||||
<p class="emotions-label">{{ $t(`contribution.emotions-label.${emotion}`) }}</p>
|
||||
<p style="display: inline" :key="PostsEmotionsCountByEmotion[emotion]">
|
||||
{{ PostsEmotionsCountByEmotion[emotion] }}x
|
||||
</p>
|
||||
{{ $t('contribution.emotions-label.emoted') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
iconPath: { type: String, default: null },
|
||||
PostsEmotionsCountByEmotion: { type: Object, default: () => {} },
|
||||
emotion: { type: String, default: null },
|
||||
},
|
||||
methods: {
|
||||
toggleEmotion(emotion) {
|
||||
this.$emit('toggleEmotion', emotion)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.emotions-flex {
|
||||
justify-content: space-evenly;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emotions-label {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.emotions-buttons {
|
||||
&:hover {
|
||||
background-color: $background-color-base;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
.emotions-mobile-space {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -13,57 +13,39 @@ describe('FilterMenu.vue', () => {
|
||||
let mocks
|
||||
let propsData
|
||||
|
||||
const createWrapper = mountMethod => {
|
||||
return mountMethod(FilterMenu, {
|
||||
propsData,
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = { $t: () => {} }
|
||||
propsData = {}
|
||||
})
|
||||
|
||||
describe('given a user', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
user: {
|
||||
id: '4711',
|
||||
},
|
||||
hashtag: null,
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(FilterMenu, { mocks, localVue, propsData })
|
||||
}
|
||||
beforeEach(() => {
|
||||
wrapper = createWrapper(mount)
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders a card', () => {
|
||||
it('does not render a card if there are no hashtags', () => {
|
||||
expect(wrapper.is('.ds-card')).toBe(true)
|
||||
})
|
||||
|
||||
describe('click "filter-by-followed-authors-only" button', () => {
|
||||
it('emits filterBubble object', () => {
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
|
||||
expect(wrapper.emitted('changeFilterBubble')).toBeTruthy()
|
||||
})
|
||||
it('renders a card if there are hashtags', () => {
|
||||
propsData.hashtag = 'Frieden'
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.is('.ds-card')).toBe(true)
|
||||
})
|
||||
|
||||
it('toggles filterBubble.author property', () => {
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
|
||||
expect(wrapper.emitted('changeFilterBubble')[0]).toEqual([
|
||||
{ author: { followedBy_some: { id: '4711' } } },
|
||||
])
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
|
||||
expect(wrapper.emitted('changeFilterBubble')[1]).toEqual([{}])
|
||||
})
|
||||
|
||||
it('makes button primary', () => {
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
|
||||
expect(
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).classes('ds-button-primary'),
|
||||
).toBe(true)
|
||||
describe('click "clear-search-button" button', () => {
|
||||
it('emits clearSearch', () => {
|
||||
wrapper.find({ name: 'clear-search-button' }).trigger('click')
|
||||
expect(wrapper.emitted().clearSearch).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,26 +1,6 @@
|
||||
<template>
|
||||
<ds-card class="filter-menu-card">
|
||||
<ds-flex>
|
||||
<ds-flex-item class="filter-menu-title">
|
||||
<ds-heading size="h3">{{ $t('filter-menu.title') }}</ds-heading>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<div class="filter-menu-buttons">
|
||||
<ds-button
|
||||
v-tooltip="{
|
||||
content: this.$t('contribution.filterFollow'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
name="filter-by-followed-authors-only"
|
||||
icon="user-plus"
|
||||
:primary="!!filterAuthorIsFollowedById"
|
||||
@click="toggleOnlyFollowed"
|
||||
/>
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<div v-if="hashtag">
|
||||
<ds-card v-show="hashtag" class="filter-menu-card">
|
||||
<div>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
@ -34,7 +14,7 @@
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
name="filter-by-followed-authors-only"
|
||||
name="clear-search-button"
|
||||
icon="close"
|
||||
@click="clearSearch"
|
||||
/>
|
||||
@ -48,31 +28,9 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
user: { type: Object, required: true },
|
||||
hashtag: { type: Object, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filter: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterAuthorIsFollowedById() {
|
||||
const { author = {} } = this.filter
|
||||
/* eslint-disable camelcase */
|
||||
const { followedBy_some = {} } = author
|
||||
const { id } = followedBy_some
|
||||
/* eslint-enable */
|
||||
return id
|
||||
},
|
||||
hashtag: { type: String, default: null },
|
||||
},
|
||||
methods: {
|
||||
toggleOnlyFollowed() {
|
||||
this.filter = this.filterAuthorIsFollowedById
|
||||
? {}
|
||||
: { author: { followedBy_some: { id: this.user.id } } }
|
||||
this.$emit('changeFilterBubble', this.filter)
|
||||
},
|
||||
clearSearch() {
|
||||
this.$emit('clearSearch')
|
||||
},
|
||||
|
||||
@ -3,23 +3,21 @@ import VTooltip from 'v-tooltip'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import FilterPosts from './FilterPosts.vue'
|
||||
import FilterPostsMenuItem from './FilterPostsMenuItems.vue'
|
||||
import { mutations } from '~/store/posts'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(VTooltip)
|
||||
localVue.use(Vuex)
|
||||
|
||||
let mutations
|
||||
let getters
|
||||
|
||||
describe('FilterPosts.vue', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
let propsData
|
||||
let menuToggle
|
||||
let allCategoriesButton
|
||||
let environmentAndNatureButton
|
||||
let consumptionAndSustainabiltyButton
|
||||
let democracyAndPoliticsButton
|
||||
|
||||
beforeEach(() => {
|
||||
@ -50,22 +48,28 @@ describe('FilterPosts.vue', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const store = new Vuex.Store({
|
||||
mutations: {
|
||||
'posts/SET_POSTS': mutations.SET_POSTS,
|
||||
},
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(FilterPosts, { mocks, localVue, propsData, store })
|
||||
mutations = {
|
||||
'postsFilter/TOGGLE_FILTER_BY_FOLLOWED': jest.fn(),
|
||||
'postsFilter/RESET_CATEGORIES': jest.fn(),
|
||||
'postsFilter/TOGGLE_CATEGORY': jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return { id: 'u34' }
|
||||
},
|
||||
'postsFilter/filteredCategoryIds': jest.fn(() => []),
|
||||
'postsFilter/filteredByUsersFollowed': jest.fn(),
|
||||
}
|
||||
const openFilterPosts = () => {
|
||||
const store = new Vuex.Store({ mutations, getters })
|
||||
const wrapper = mount(FilterPosts, { mocks, localVue, propsData, store })
|
||||
menuToggle = wrapper.findAll('a').at(0)
|
||||
menuToggle.trigger('click')
|
||||
})
|
||||
return wrapper
|
||||
}
|
||||
|
||||
it('groups the categories by pair', () => {
|
||||
const wrapper = openFilterPosts()
|
||||
expect(wrapper.vm.chunk).toEqual([
|
||||
[
|
||||
{ id: 'cat4', name: 'Environment & Nature', icon: 'tree' },
|
||||
@ -76,59 +80,43 @@ describe('FilterPosts.vue', () => {
|
||||
})
|
||||
|
||||
it('starts with all categories button active', () => {
|
||||
const wrapper = openFilterPosts()
|
||||
allCategoriesButton = wrapper.findAll('button').at(0)
|
||||
expect(allCategoriesButton.attributes().class).toContain('ds-button-primary')
|
||||
})
|
||||
|
||||
it('adds a categories id to selectedCategoryIds when clicked', () => {
|
||||
it('calls TOGGLE_CATEGORY when clicked', () => {
|
||||
const wrapper = openFilterPosts()
|
||||
environmentAndNatureButton = wrapper.findAll('button').at(1)
|
||||
environmentAndNatureButton.trigger('click')
|
||||
const filterPostsMenuItem = wrapper.find(FilterPostsMenuItem)
|
||||
expect(filterPostsMenuItem.vm.selectedCategoryIds).toEqual(['cat4'])
|
||||
expect(mutations['postsFilter/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4')
|
||||
})
|
||||
|
||||
it('sets primary to true when the button is clicked', () => {
|
||||
it('sets category button attribute `primary` when corresponding category is filtered', () => {
|
||||
getters['postsFilter/filteredCategoryIds'] = jest.fn(() => ['cat9'])
|
||||
const wrapper = openFilterPosts()
|
||||
democracyAndPoliticsButton = wrapper.findAll('button').at(3)
|
||||
democracyAndPoliticsButton.trigger('click')
|
||||
expect(democracyAndPoliticsButton.attributes().class).toContain('ds-button-primary')
|
||||
})
|
||||
|
||||
it('queries a post by its categories', () => {
|
||||
consumptionAndSustainabiltyButton = wrapper.findAll('button').at(2)
|
||||
consumptionAndSustainabiltyButton.trigger('click')
|
||||
expect(mocks.$apollo.query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
filter: { categories_some: { id_in: ['cat15'] } },
|
||||
first: expect.any(Number),
|
||||
offset: expect.any(Number),
|
||||
},
|
||||
}),
|
||||
)
|
||||
it('sets "filter-by-followed-authors-only" button attribute `primary`', () => {
|
||||
getters['postsFilter/filteredByUsersFollowed'] = jest.fn(() => true)
|
||||
const wrapper = openFilterPosts()
|
||||
expect(
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).classes('ds-button-primary'),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('supports a query of multiple categories', () => {
|
||||
environmentAndNatureButton = wrapper.findAll('button').at(1)
|
||||
environmentAndNatureButton.trigger('click')
|
||||
consumptionAndSustainabiltyButton = wrapper.findAll('button').at(2)
|
||||
consumptionAndSustainabiltyButton.trigger('click')
|
||||
expect(mocks.$apollo.query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
filter: { categories_some: { id_in: ['cat4', 'cat15'] } },
|
||||
first: expect.any(Number),
|
||||
offset: expect.any(Number),
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
describe('click "filter-by-followed-authors-only" button', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = openFilterPosts()
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
|
||||
})
|
||||
|
||||
it('toggles the categoryIds when clicked more than once', () => {
|
||||
environmentAndNatureButton = wrapper.findAll('button').at(1)
|
||||
environmentAndNatureButton.trigger('click')
|
||||
environmentAndNatureButton.trigger('click')
|
||||
const filterPostsMenuItem = wrapper.find(FilterPostsMenuItem)
|
||||
expect(filterPostsMenuItem.vm.selectedCategoryIds).toEqual([])
|
||||
it('calls TOGGLE_FILTER_BY_FOLLOWED', () => {
|
||||
expect(mutations['postsFilter/TOGGLE_FILTER_BY_FOLLOWED']).toHaveBeenCalledWith({}, 'u34')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,15 +5,14 @@
|
||||
<ds-icon style="margin: 7px 0px 0px 2px" size="xx-small" name="angle-down" />
|
||||
</a>
|
||||
<template slot="popover">
|
||||
<filter-posts-menu-items :chunk="chunk" @filterPosts="filterPosts" />
|
||||
<filter-posts-menu-items :chunk="chunk" :user="currentUser" />
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import { filterPosts } from '~/graphql/PostQuery.js'
|
||||
import { mapMutations } from 'vuex'
|
||||
import { mapGetters } from 'vuex'
|
||||
import FilterPostsMenuItems from '~/components/FilterPosts/FilterPostsMenuItems'
|
||||
|
||||
export default {
|
||||
@ -26,36 +25,13 @@ export default {
|
||||
offset: { type: [String, Number] },
|
||||
categories: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageSize: 12,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
chunk() {
|
||||
return _.chunk(this.categories, 2)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
setPosts: 'posts/SET_POSTS',
|
||||
}),
|
||||
filterPosts(categoryIds) {
|
||||
const filter = categoryIds.length ? { categories_some: { id_in: categoryIds } } : {}
|
||||
this.$apollo
|
||||
.query({
|
||||
query: filterPosts(this.$i18n),
|
||||
variables: {
|
||||
filter: filter,
|
||||
first: this.pageSize,
|
||||
offset: 0,
|
||||
},
|
||||
})
|
||||
.then(({ data: { Post } }) => {
|
||||
this.setPosts(Post)
|
||||
})
|
||||
.catch(error => this.$toast.error(error.message))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<ds-container>
|
||||
<ds-space />
|
||||
<ds-flex id="filter-posts-header">
|
||||
<ds-heading tag="h4">{{ $t('filter-posts.header') }}</ds-heading>
|
||||
<ds-heading tag="h4">{{ $t('filter-posts.categories.header') }}</ds-heading>
|
||||
<ds-space margin-bottom="large" />
|
||||
</ds-flex>
|
||||
<ds-flex>
|
||||
@ -15,11 +15,11 @@
|
||||
<ds-flex-item width="100%">
|
||||
<ds-button
|
||||
icon="check"
|
||||
@click.stop.prevent="toggleCategory()"
|
||||
:primary="allCategories"
|
||||
@click.stop.prevent="resetCategories"
|
||||
:primary="!filteredCategoryIds.length"
|
||||
/>
|
||||
<ds-flex-item>
|
||||
<label class="category-labels">{{ $t('filter-posts.all') }}</label>
|
||||
<label class="category-labels">{{ $t('filter-posts.categories.all') }}</label>
|
||||
</ds-flex-item>
|
||||
<ds-space />
|
||||
</ds-flex-item>
|
||||
@ -40,7 +40,7 @@
|
||||
<ds-flex-item width="100%" class="categories-menu-item">
|
||||
<ds-button
|
||||
:icon="category.icon"
|
||||
:primary="isActive(category.id)"
|
||||
:primary="filteredCategoryIds.includes(category.id)"
|
||||
@click.stop.prevent="toggleCategory(category.id)"
|
||||
/>
|
||||
<ds-space margin-bottom="small" />
|
||||
@ -55,42 +55,68 @@
|
||||
</ds-flex>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<ds-space />
|
||||
<ds-flex id="filter-posts-by-followers-header">
|
||||
<ds-heading tag="h4">{{ $t('filter-posts.general.header') }}</ds-heading>
|
||||
<ds-space margin-bottom="large" />
|
||||
</ds-flex>
|
||||
<ds-flex>
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '100%', md: '100%', lg: '10%' }"
|
||||
class="categories-menu-item"
|
||||
>
|
||||
<ds-flex>
|
||||
<ds-flex-item width="10%" />
|
||||
<ds-flex-item width="100%">
|
||||
<div class="follow-button">
|
||||
<ds-button
|
||||
v-tooltip="{
|
||||
content: this.$t('contribution.filterFollow'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
name="filter-by-followed-authors-only"
|
||||
icon="user-plus"
|
||||
:primary="filteredByUsersFollowed"
|
||||
@click="toggleFilteredByFollowed(user.id)"
|
||||
/>
|
||||
<ds-flex-item>
|
||||
<label class="follow-label">{{ $t('filter-posts.followers.label') }}</label>
|
||||
</ds-flex-item>
|
||||
<ds-space />
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-flex-item>
|
||||
<ds-space margin-bottom="large" />
|
||||
</ds-flex>
|
||||
</ds-container>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
user: { type: Object, required: true },
|
||||
chunk: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedCategoryIds: [],
|
||||
allCategories: true,
|
||||
filter: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
filteredCategoryIds: 'postsFilter/filteredCategoryIds',
|
||||
filteredByUsersFollowed: 'postsFilter/filteredByUsersFollowed',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
isActive(id) {
|
||||
const index = this.selectedCategoryIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
toggleCategory(id) {
|
||||
if (!id) {
|
||||
this.selectedCategoryIds = []
|
||||
this.allCategories = true
|
||||
} else {
|
||||
const index = this.selectedCategoryIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
this.selectedCategoryIds.splice(index, 1)
|
||||
} else {
|
||||
this.selectedCategoryIds.push(id)
|
||||
}
|
||||
this.allCategories = false
|
||||
}
|
||||
this.$emit('filterPosts', this.selectedCategoryIds)
|
||||
},
|
||||
...mapMutations({
|
||||
toggleFilteredByFollowed: 'postsFilter/TOGGLE_FILTER_BY_FOLLOWED',
|
||||
resetCategories: 'postsFilter/RESET_CATEGORIES',
|
||||
toggleCategory: 'postsFilter/TOGGLE_CATEGORY',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -99,6 +125,10 @@ export default {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#filter-posts-by-followers-header {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.categories-menu-item {
|
||||
text-align: center;
|
||||
}
|
||||
@ -107,7 +137,8 @@ export default {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-labels {
|
||||
.category-labels,
|
||||
.follow-label {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
@ -122,5 +153,8 @@ export default {
|
||||
#filter-posts-header {
|
||||
text-align: center;
|
||||
}
|
||||
.follow-button {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -28,8 +28,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
name: 'ConfirmModal',
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
type: { type: String, required: true },
|
||||
|
||||
@ -27,9 +27,13 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
name: 'ReportModal',
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
type: { type: String, required: true },
|
||||
|
||||
@ -54,10 +54,12 @@
|
||||
<script>
|
||||
import PasswordStrength from '../Password/Strength'
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
PasswordStrength,
|
||||
},
|
||||
props: {
|
||||
|
||||
@ -48,8 +48,12 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
|
||||
@ -71,6 +71,7 @@
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import PasswordStrength from '../Password/Strength'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
||||
|
||||
export const SignupVerificationMutation = gql`
|
||||
@ -85,6 +86,7 @@ export const SignupVerificationMutation = gql`
|
||||
export default {
|
||||
components: {
|
||||
PasswordStrength,
|
||||
SweetalertIcon,
|
||||
},
|
||||
data() {
|
||||
const passwordForm = PasswordForm({ translate: this.$t })
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export const SignupMutation = gql`
|
||||
mutation($email: String!) {
|
||||
@ -72,6 +73,9 @@ export const SignupByInvitationMutation = gql`
|
||||
}
|
||||
`
|
||||
export default {
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
token: { type: String, default: null },
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ds-space margin="large" style="text-align: center">
|
||||
<ds-space margin="xx-small" class="text-align-center">
|
||||
<ds-button
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
@ -88,4 +88,7 @@ export default {
|
||||
.shout-button-text {
|
||||
user-select: none;
|
||||
}
|
||||
.text-align-center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -60,5 +60,31 @@ export default () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
AddPostEmotionsMutation: gql`
|
||||
mutation($to: _PostInput!, $data: _EMOTEDInput!) {
|
||||
AddPostEmotions(to: $to, data: $data) {
|
||||
emotion
|
||||
from {
|
||||
id
|
||||
}
|
||||
to {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
RemovePostEmotionsMutation: gql`
|
||||
mutation($to: _PostInput!, $data: _EMOTEDInput!) {
|
||||
RemovePostEmotions(to: $to, data: $data) {
|
||||
emotion
|
||||
from {
|
||||
id
|
||||
}
|
||||
to {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,6 +71,7 @@ export default i18n => {
|
||||
}
|
||||
shoutedCount
|
||||
shoutedByCurrentUser
|
||||
emotionsCount
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -120,3 +121,11 @@ export const filterPosts = i18n => {
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const PostsEmotionsByCurrentUser = () => {
|
||||
return gql`
|
||||
query PostsEmotionsByCurrentUser($postId: ID!) {
|
||||
PostsEmotionsByCurrentUser(postId: $postId)
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@ -38,7 +38,12 @@
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
>
|
||||
<no-ssr>
|
||||
<filter-posts placement="top-start" offset="8" :categories="categories" />
|
||||
<filter-posts
|
||||
v-show="showFilterPostsDropdown"
|
||||
placement="top-start"
|
||||
offset="8"
|
||||
:categories="categories"
|
||||
/>
|
||||
</no-ssr>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '10%', lg: '2%' }" />
|
||||
@ -142,7 +147,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import SearchInput from '~/components/SearchInput.vue'
|
||||
import Modal from '~/components/Modal'
|
||||
@ -178,6 +183,8 @@ export default {
|
||||
isAdmin: 'auth/isAdmin',
|
||||
quickSearchResults: 'search/quickResults',
|
||||
quickSearchPending: 'search/quickPending',
|
||||
usersFollowedFilter: 'posts/usersFollowedFilter',
|
||||
categoriesFilter: 'posts/categoriesFilter',
|
||||
}),
|
||||
userName() {
|
||||
const { name } = this.user || {}
|
||||
@ -215,6 +222,10 @@ export default {
|
||||
}
|
||||
return routes
|
||||
},
|
||||
showFilterPostsDropdown() {
|
||||
const [firstRoute] = this.$route.matched
|
||||
return firstRoute.name === 'index'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
Category(category) {
|
||||
@ -227,6 +238,9 @@ export default {
|
||||
quickSearch: 'search/quickSearch',
|
||||
fetchPosts: 'posts/fetchPosts',
|
||||
}),
|
||||
...mapMutations({
|
||||
setFilteredByFollowers: 'posts/SET_FILTERED_BY_FOLLOWERS',
|
||||
}),
|
||||
goToPost(item) {
|
||||
this.$nextTick(() => {
|
||||
this.$router.push({
|
||||
@ -247,7 +261,14 @@ export default {
|
||||
},
|
||||
redirectToRoot() {
|
||||
this.$router.replace('/')
|
||||
this.fetchPosts({ i18n: this.$i18n, filter: {} })
|
||||
this.fetchPosts({
|
||||
i18n: this.$i18n,
|
||||
filter: {
|
||||
...this.usersFollowedFilter,
|
||||
...this.categoriesFilter,
|
||||
...this.filter,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
|
||||
@ -5,8 +5,16 @@
|
||||
"clearSearch": "Suche löschen"
|
||||
},
|
||||
"filter-posts": {
|
||||
"header": "Themenkategorien",
|
||||
"all": "Alle"
|
||||
"categories": {
|
||||
"header": "Themenkategorien",
|
||||
"all": "Alle"
|
||||
},
|
||||
"general": {
|
||||
"header": "Filtern nach..."
|
||||
},
|
||||
"followers": {
|
||||
"label": "Benutzern, denen ich folge"
|
||||
}
|
||||
},
|
||||
"site": {
|
||||
"made": "Mit ❤ gemacht",
|
||||
@ -165,7 +173,7 @@
|
||||
"commentsCount": "Meine {count} Kommentare löschen",
|
||||
"accountDescription": "Sei dir bewusst, dass deine Beiträge und Kommentare für unsere Community wichtig sind. Wenn du sie trotzdem löschen möchtest, musst du sie unten markieren.",
|
||||
"accountWarning": "Dein Konto, deine Beiträge oder Kommentare kannst du nach dem Löschen <b>WEDER VERWALTEN NOCH WIEDERHERSTELLEN!</b>",
|
||||
"success": "Konto erfolgreich gelöscht",
|
||||
"success": "Konto erfolgreich gelöscht!",
|
||||
"pleaseConfirm": "<b class='is-danger'>Zerstörerische Aktion!</b> Gib <b>{confirm}</b> ein, um zu bestätigen."
|
||||
},
|
||||
"organizations": {
|
||||
@ -207,6 +215,7 @@
|
||||
},
|
||||
"table": {
|
||||
"columns": {
|
||||
"number": "Nr.",
|
||||
"name": "Name",
|
||||
"slug": "Slug",
|
||||
"role": "Rolle",
|
||||
@ -227,7 +236,9 @@
|
||||
"postCount": "Beiträge"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Schlagworte",
|
||||
"name": "Hashtags",
|
||||
"number": "Nr.",
|
||||
"nameOfHashtag": "Name",
|
||||
"tagCountUnique": "Benutzer",
|
||||
"tagCount": "Beiträge"
|
||||
},
|
||||
@ -243,7 +254,12 @@
|
||||
"post": {
|
||||
"name": "Beitrag",
|
||||
"moreInfo": {
|
||||
"name": "Mehr Info"
|
||||
"name": "Mehr Info",
|
||||
"title": "Mehr Informationen",
|
||||
"description": "Hier findest du weitere Infos zum Thema.",
|
||||
"titleOfCategoriesSection": "Kategorien",
|
||||
"titleOfHashtagsSection": "Hashtags",
|
||||
"titleOfRelatedContributionsSection": "Verwandte Beiträge"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Aktiv werden"
|
||||
@ -321,7 +337,7 @@
|
||||
"disable": {
|
||||
"submit": "Deaktivieren",
|
||||
"cancel": "Abbrechen",
|
||||
"success": "Erfolgreich deaktiviert",
|
||||
"success": "Erfolgreich deaktiviert!",
|
||||
"user": {
|
||||
"title": "Nutzer sperren",
|
||||
"type": "Nutzer",
|
||||
@ -420,6 +436,14 @@
|
||||
"languageSelectLabel": "Sprache",
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Kategorien ausgewählt"
|
||||
},
|
||||
"emotions-label": {
|
||||
"funny": "Lustig",
|
||||
"happy": "Glücklich",
|
||||
"surprised": "Erstaunt",
|
||||
"cry": "Zum Weinen",
|
||||
"angry": "Verärgert",
|
||||
"emoted": "angegeben"
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
|
||||
@ -5,8 +5,16 @@
|
||||
"clearSearch": "Clear search"
|
||||
},
|
||||
"filter-posts": {
|
||||
"header": "Categories of Content",
|
||||
"all": "All"
|
||||
"categories": {
|
||||
"header": "Categories of Content",
|
||||
"all": "All"
|
||||
},
|
||||
"general": {
|
||||
"header": "Filter by..."
|
||||
},
|
||||
"followers": {
|
||||
"label": "Users I follow"
|
||||
}
|
||||
},
|
||||
"site": {
|
||||
"made": "Made with ❤",
|
||||
@ -28,7 +36,7 @@
|
||||
"newest": "Newest",
|
||||
"oldest": "Oldest",
|
||||
"popular": "Popular",
|
||||
"commented": "most Commented"
|
||||
"commented": "Most commented"
|
||||
},
|
||||
"login": {
|
||||
"copy": "If you already have a human-connection account, login here.",
|
||||
@ -99,7 +107,7 @@
|
||||
"follow": "Follow",
|
||||
"followers": "Followers",
|
||||
"following": "Following",
|
||||
"shouted": "Recommended",
|
||||
"shouted": "Shouted",
|
||||
"commented": "Commented",
|
||||
"userAnonym": "Anonymous",
|
||||
"socialMedia": "Where else can I find",
|
||||
@ -160,12 +168,12 @@
|
||||
"name": "Download Data"
|
||||
},
|
||||
"deleteUserAccount": {
|
||||
"name": "Delete Data",
|
||||
"name": "Delete data",
|
||||
"contributionsCount": "Delete my {count} posts",
|
||||
"commentsCount": "Delete my {count} comments",
|
||||
"accountDescription": "Be aware that your Post and Comments are important to our community. If you still choose to delete them, you have to mark them below.",
|
||||
"accountWarning": "You <b>CAN'T MANAGE</b> and <b>CAN'T RECOVER</b> your Account, Posts, or Comments after deleting your account!",
|
||||
"success": "Account successfully deleted",
|
||||
"success": "Account successfully deleted!",
|
||||
"pleaseConfirm": "<b class='is-danger'>Destructive action!</b> Type <b>{confirm}</b> to confirm"
|
||||
},
|
||||
"organizations": {
|
||||
@ -195,7 +203,7 @@
|
||||
"projects": "Projects",
|
||||
"invites": "Invites",
|
||||
"follows": "Follows",
|
||||
"shouts": "Recommended"
|
||||
"shouts": "Shouts"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizations"
|
||||
@ -207,6 +215,7 @@
|
||||
},
|
||||
"table": {
|
||||
"columns": {
|
||||
"number": "No.",
|
||||
"name": "Name",
|
||||
"slug": "Slug",
|
||||
"role": "Role",
|
||||
@ -227,7 +236,9 @@
|
||||
"postCount": "Posts"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Tags",
|
||||
"name": "Hashtags",
|
||||
"number": "No.",
|
||||
"nameOfHashtag": "Name",
|
||||
"tagCountUnique": "Users",
|
||||
"tagCount": "Posts"
|
||||
},
|
||||
@ -243,7 +254,12 @@
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"moreInfo": {
|
||||
"name": "More info"
|
||||
"name": "More info",
|
||||
"title": "More information",
|
||||
"description": "Here you can find more information about this topic.",
|
||||
"titleOfCategoriesSection": "Categories",
|
||||
"titleOfHashtagsSection": "Hashtags",
|
||||
"titleOfRelatedContributionsSection": "Related posts"
|
||||
},
|
||||
"takeAction": {
|
||||
"name": "Take action"
|
||||
@ -321,7 +337,7 @@
|
||||
"disable": {
|
||||
"submit": "Disable",
|
||||
"cancel": "Cancel",
|
||||
"success": "Disabled successfully",
|
||||
"success": "Disabled successfully!",
|
||||
"user": {
|
||||
"title": "Disable User",
|
||||
"type": "User",
|
||||
@ -409,7 +425,7 @@
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Upload successful"
|
||||
"submitted": "Upload successful!"
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
@ -420,6 +436,14 @@
|
||||
"languageSelectLabel": "Language",
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected"
|
||||
},
|
||||
"emotions-label": {
|
||||
"funny": "Funny",
|
||||
"happy": "Happy",
|
||||
"surprised": "Surprised",
|
||||
"cry": "Cry",
|
||||
"angry": "Angry",
|
||||
"emoted": "emoted"
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
|
||||
@ -115,7 +115,6 @@ module.exports = {
|
||||
{ src: '~/plugins/v-tooltip.js', ssr: false },
|
||||
{ src: '~/plugins/izi-toast.js', ssr: false },
|
||||
{ src: '~/plugins/vue-filters.js' },
|
||||
{ src: '~/plugins/vue-sweetalert-icons.js' },
|
||||
],
|
||||
|
||||
router: {
|
||||
|
||||
@ -56,8 +56,8 @@
|
||||
"@nuxtjs/dotenv": "~1.4.0",
|
||||
"@nuxtjs/style-resources": "~1.0.0",
|
||||
"accounting": "~0.4.1",
|
||||
"apollo-cache-inmemory": "~1.6.2",
|
||||
"apollo-client": "~2.6.3",
|
||||
"apollo-cache-inmemory": "~1.6.3",
|
||||
"apollo-client": "~2.6.4",
|
||||
"cookie-universal-nuxt": "~2.0.17",
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-beta.4",
|
||||
@ -76,18 +76,18 @@
|
||||
"tiptap-extensions": "~1.26.1",
|
||||
"v-tooltip": "~2.0.2",
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-izitoast": "1.1.2",
|
||||
"vue-izitoast": "roschaefer/vue-izitoast#patch-1",
|
||||
"vuex-i18n": "~1.13.1",
|
||||
"vue-sweetalert-icons": "~4.0.0",
|
||||
"vue-sweetalert-icons": "~4.2.0",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.5.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/preset-env": "~7.5.5",
|
||||
"@storybook/addon-a11y": "^5.1.9",
|
||||
"@storybook/addon-actions": "^5.1.9",
|
||||
"@storybook/vue": "~5.1.9",
|
||||
"@storybook/addon-a11y": "^5.1.11",
|
||||
"@storybook/addon-actions": "^5.1.11",
|
||||
"@storybook/vue": "~5.1.11",
|
||||
"@vue/cli-shared-utils": "~3.10.0",
|
||||
"@vue/eslint-config-prettier": "~5.0.0",
|
||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||
@ -97,14 +97,14 @@
|
||||
"babel-jest": "~24.8.0",
|
||||
"babel-loader": "~8.0.6",
|
||||
"babel-preset-vue": "~2.0.2",
|
||||
"css-loader": "~2.1.1",
|
||||
"core-js": "~2.6.9",
|
||||
"css-loader": "~2.1.1",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~6.0.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-loader": "~2.2.1",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
"eslint-plugin-jest": "~22.14.1",
|
||||
"eslint-plugin-jest": "~22.15.1",
|
||||
"eslint-plugin-node": "~9.1.0",
|
||||
"eslint-plugin-prettier": "~3.1.0",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
@ -117,7 +117,7 @@
|
||||
"node-sass": "~4.12.0",
|
||||
"nodemon": "~1.19.1",
|
||||
"prettier": "~1.18.2",
|
||||
"sass-loader": "~7.1.0",
|
||||
"sass-loader": "~7.2.0",
|
||||
"style-loader": "~0.23.1",
|
||||
"style-resources-loader": "~1.2.1",
|
||||
"tippy.js": "^4.3.5",
|
||||
|
||||
@ -31,7 +31,7 @@ export default {
|
||||
},
|
||||
apollo: {
|
||||
Category: {
|
||||
query: gql(`
|
||||
query: gql`
|
||||
query {
|
||||
Category(orderBy: postCount_desc) {
|
||||
id
|
||||
@ -41,7 +41,7 @@ export default {
|
||||
postCount
|
||||
}
|
||||
}
|
||||
`),
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ export default {
|
||||
},
|
||||
apollo: {
|
||||
statistics: {
|
||||
query: gql(`
|
||||
query: gql`
|
||||
query {
|
||||
statistics {
|
||||
countUsers
|
||||
@ -147,7 +147,7 @@ export default {
|
||||
countShouts
|
||||
}
|
||||
}
|
||||
`),
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -2,7 +2,12 @@
|
||||
<ds-card :header="$t('admin.tags.name')">
|
||||
<ds-table :data="Tag" :fields="fields" condensed>
|
||||
<template slot="id" slot-scope="scope">
|
||||
{{ scope.index + 1 }}
|
||||
{{ scope.index + 1 }}.
|
||||
</template>
|
||||
<template slot="name" slot-scope="scope">
|
||||
<nuxt-link :to="{ path: '/', query: { hashtag: scope.row.id } }">
|
||||
<b>#{{ scope.row.name | truncate(20) }}</b>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</ds-table>
|
||||
</ds-card>
|
||||
@ -20,8 +25,8 @@ export default {
|
||||
computed: {
|
||||
fields() {
|
||||
return {
|
||||
id: '#',
|
||||
name: 'Name',
|
||||
id: this.$t('admin.tags.number'),
|
||||
name: this.$t('admin.tags.name'),
|
||||
taggedCountUnique: {
|
||||
label: this.$t('admin.tags.tagCountUnique'),
|
||||
align: 'right',
|
||||
@ -35,7 +40,7 @@ export default {
|
||||
},
|
||||
apollo: {
|
||||
Tag: {
|
||||
query: gql(`
|
||||
query: gql`
|
||||
query {
|
||||
Tag(first: 20, orderBy: taggedCountUnique_desc) {
|
||||
id
|
||||
@ -44,7 +49,7 @@ export default {
|
||||
taggedCountUnique
|
||||
}
|
||||
}
|
||||
`),
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<ds-card v-if="User && User.length">
|
||||
<ds-table :data="User" :fields="fields" condensed>
|
||||
<template slot="index" slot-scope="scope">
|
||||
{{ scope.row.index }}.
|
||||
{{ scope.row.index + 1 }}.
|
||||
</template>
|
||||
<template slot="name" slot-scope="scope">
|
||||
<nuxt-link
|
||||
@ -57,9 +57,7 @@
|
||||
</ds-flex>
|
||||
</ds-card>
|
||||
<ds-card v-else>
|
||||
<ds-placeholder>
|
||||
{{ $t('admin.users.empty') }}
|
||||
</ds-placeholder>
|
||||
<ds-placeholder>{{ $t('admin.users.empty') }}</ds-placeholder>
|
||||
</ds-card>
|
||||
</div>
|
||||
</template>
|
||||
@ -92,7 +90,7 @@ export default {
|
||||
},
|
||||
fields() {
|
||||
return {
|
||||
index: '#',
|
||||
index: this.$t('admin.users.table.columns.number'),
|
||||
name: this.$t('admin.users.table.columns.name'),
|
||||
slug: this.$t('admin.users.table.columns.slug'),
|
||||
createdAt: this.$t('admin.users.table.columns.createdAt'),
|
||||
@ -118,20 +116,26 @@ export default {
|
||||
apollo: {
|
||||
User: {
|
||||
query() {
|
||||
return gql(`
|
||||
query($filter: _UserFilter, $first: Int, $offset: Int, $email: String) {
|
||||
User(email: $email, filter: $filter, first: $first, offset: $offset, orderBy: createdAt_desc) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
role
|
||||
createdAt
|
||||
contributionsCount
|
||||
commentedCount
|
||||
shoutedCount
|
||||
return gql`
|
||||
query($filter: _UserFilter, $first: Int, $offset: Int, $email: String) {
|
||||
User(
|
||||
email: $email
|
||||
filter: $filter
|
||||
first: $first
|
||||
offset: $offset
|
||||
orderBy: createdAt_desc
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
role
|
||||
createdAt
|
||||
contributionsCount
|
||||
commentedCount
|
||||
shoutedCount
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
`
|
||||
},
|
||||
variables() {
|
||||
const { offset, first, email, filter } = this
|
||||
|
||||
@ -26,17 +26,7 @@ describe('PostIndex', () => {
|
||||
beforeEach(() => {
|
||||
store = new Vuex.Store({
|
||||
getters: {
|
||||
'posts/posts': () => {
|
||||
return [
|
||||
{
|
||||
id: 'p23',
|
||||
name: 'It is a post',
|
||||
author: {
|
||||
id: 'u1',
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
'postsFilter/postsFilter': () => ({}),
|
||||
'auth/user': () => {
|
||||
return { id: 'u23' }
|
||||
},
|
||||
@ -95,22 +85,11 @@ describe('PostIndex', () => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('refetches Posts when changeFilterBubble is emitted', () => {
|
||||
wrapper.find(FilterMenu).vm.$emit('changeFilterBubble')
|
||||
expect(mocks.$apollo.queries.Post.refetch).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('clears the search when the filter menu emits clearSearch', () => {
|
||||
wrapper.find(FilterMenu).vm.$emit('clearSearch')
|
||||
expect(wrapper.vm.hashtag).toBeNull()
|
||||
})
|
||||
|
||||
it('calls the changeFilterBubble if there are hasthags in the route query', () => {
|
||||
mocks.$route.query.hashtag = { id: 'hashtag' }
|
||||
wrapper = Wrapper()
|
||||
expect(mocks.$apollo.queries.Post.refetch).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(PostIndex, {
|
||||
@ -128,12 +107,9 @@ describe('PostIndex', () => {
|
||||
expect(wrapper.vm.sorting).toEqual('createdAt_desc')
|
||||
})
|
||||
|
||||
it('loads more posts when a user clicks on the load more button', () => {
|
||||
wrapper
|
||||
.findAll('button')
|
||||
.at(2)
|
||||
.trigger('click')
|
||||
expect(mocks.$apollo.queries.Post.fetchMore).toHaveBeenCalledTimes(1)
|
||||
it('updates offset when a user clicks on the load more button', () => {
|
||||
wrapper.find('.load-more button').trigger('click')
|
||||
expect(wrapper.vm.offset).toEqual(12)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,12 +2,7 @@
|
||||
<div>
|
||||
<ds-flex :width="{ base: '100%' }" gutter="base">
|
||||
<ds-flex-item>
|
||||
<filter-menu
|
||||
:user="currentUser"
|
||||
@changeFilterBubble="changeFilterBubble"
|
||||
:hashtag="hashtag"
|
||||
@clearSearch="clearSearch"
|
||||
/>
|
||||
<filter-menu :hashtag="hashtag" @clearSearch="clearSearch" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<div class="sorting-dropdown">
|
||||
@ -21,7 +16,7 @@
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
<hc-post-card
|
||||
v-for="(post, index) in posts"
|
||||
v-for="post in posts"
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
|
||||
@ -38,7 +33,7 @@
|
||||
primary
|
||||
/>
|
||||
</no-ssr>
|
||||
<hc-load-more v-if="true" :loading="$apollo.loading" @click="showMoreContributions" />
|
||||
<hc-load-more v-if="hasMore" :loading="$apollo.loading" @click="showMoreContributions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -47,7 +42,7 @@ import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import HcPostCard from '~/components/PostCard'
|
||||
import HcLoadMore from '~/components/LoadMore.vue'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { filterPosts } from '~/graphql/PostQuery.js'
|
||||
|
||||
export default {
|
||||
@ -59,10 +54,11 @@ export default {
|
||||
data() {
|
||||
const { hashtag = null } = this.$route.query
|
||||
return {
|
||||
posts: [],
|
||||
hasMore: true,
|
||||
// Initialize your apollo data
|
||||
page: 1,
|
||||
offset: 0,
|
||||
pageSize: 12,
|
||||
filter: {},
|
||||
hashtag,
|
||||
placeholder: this.$t('sorting.newest'),
|
||||
selected: this.$t('sorting.newest'),
|
||||
@ -96,55 +92,37 @@ export default {
|
||||
],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.hashtag) {
|
||||
this.changeFilterBubble({ tags_some: { name: this.hashtag } })
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
Post(post) {
|
||||
this.setPosts(this.Post)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
posts: 'posts/posts',
|
||||
postsFilter: 'postsFilter/postsFilter',
|
||||
}),
|
||||
tags() {
|
||||
return this.posts ? this.posts.tags.map(tag => tag.name) : '-'
|
||||
},
|
||||
offset() {
|
||||
return (this.page - 1) * this.pageSize
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
setPosts: 'posts/SET_POSTS',
|
||||
}),
|
||||
changeFilterBubble(filter) {
|
||||
finalFilters() {
|
||||
let filter = this.postsFilter
|
||||
if (this.hashtag) {
|
||||
filter = {
|
||||
...filter,
|
||||
tags_some: { name: this.hashtag },
|
||||
}
|
||||
}
|
||||
this.filter = filter
|
||||
this.$apollo.queries.Post.refetch()
|
||||
return filter
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
postsFilter() {
|
||||
this.offset = 0
|
||||
this.posts = []
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleOnlySorting(x) {
|
||||
this.offset = 0
|
||||
this.posts = []
|
||||
this.sortingIcon = x.icons
|
||||
this.sorting = x.order
|
||||
this.$apollo.queries.Post.refetch()
|
||||
},
|
||||
clearSearch() {
|
||||
this.$router.push({ path: '/' })
|
||||
this.hashtag = null
|
||||
delete this.filter.tags_some
|
||||
this.changeFilterBubble(this.filter)
|
||||
},
|
||||
uniq(items, field = 'id') {
|
||||
return uniqBy(items, field)
|
||||
},
|
||||
href(post) {
|
||||
return this.$router.resolve({
|
||||
@ -153,31 +131,12 @@ export default {
|
||||
}).href
|
||||
},
|
||||
showMoreContributions() {
|
||||
// this.page++
|
||||
// Fetch more data and transform the original result
|
||||
this.page++
|
||||
this.$apollo.queries.Post.fetchMore({
|
||||
variables: {
|
||||
filter: this.filter,
|
||||
first: this.pageSize,
|
||||
offset: this.offset,
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
let output = { Post: this.Post }
|
||||
output.Post = [...previousResult.Post, ...fetchMoreResult.Post]
|
||||
return output
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
})
|
||||
this.offset += this.pageSize
|
||||
},
|
||||
deletePost(_index, postId) {
|
||||
this.Post = this.Post.filter(post => {
|
||||
this.posts = this.posts.filter(post => {
|
||||
return post.id !== postId
|
||||
})
|
||||
// Why "uniq(Post)" is used in the array for list creation?
|
||||
// Ideal solution here:
|
||||
// this.Post.splice(index, 1)
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
@ -186,12 +145,21 @@ export default {
|
||||
return filterPosts(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
filter: this.filter,
|
||||
const result = {
|
||||
filter: this.finalFilters,
|
||||
first: this.pageSize,
|
||||
offset: 0,
|
||||
offset: this.offset,
|
||||
orderBy: this.sorting,
|
||||
}
|
||||
return result
|
||||
},
|
||||
update({ Post }) {
|
||||
// TODO: find out why `update` gets called twice initially.
|
||||
// We have to filter for uniq posts only because we get the same
|
||||
// result set twice.
|
||||
this.hasMore = Post.length >= this.pageSize
|
||||
const posts = uniqBy([...this.posts, ...Post], 'id')
|
||||
this.posts = posts
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
|
||||
@ -31,7 +31,7 @@ describe('PostSlug', () => {
|
||||
$filters: {
|
||||
truncate: a => a,
|
||||
},
|
||||
// If you mocking router, than don't use VueRouter with lacalVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
||||
// If you are mocking the router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
||||
$router: {
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
|
||||
@ -40,14 +40,30 @@
|
||||
<ds-space margin="xx-small" />
|
||||
<hc-tag v-for="tag in post.tags" :key="tag.id" :name="tag.name" />
|
||||
</div>
|
||||
<!-- Shout Button -->
|
||||
<hc-shout-button
|
||||
v-if="post.author"
|
||||
:disabled="isAuthor(post.author.id)"
|
||||
:count="post.shoutedCount"
|
||||
:is-shouted="post.shoutedByCurrentUser"
|
||||
:post-id="post.id"
|
||||
/>
|
||||
<ds-space margin-top="x-large">
|
||||
<ds-flex :gutter="{ lg: 'small' }">
|
||||
<ds-flex-item
|
||||
:width="{ lg: '75%', md: '75%', sm: '75%' }"
|
||||
class="emotions-buttons-mobile"
|
||||
>
|
||||
<hc-emotions :post="post" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ lg: '10%', md: '3%', sm: '3%' }" />
|
||||
<!-- Shout Button -->
|
||||
<ds-flex-item
|
||||
:width="{ lg: '15%', md: '22%', sm: '22%', base: '100%' }"
|
||||
class="shout-button"
|
||||
>
|
||||
<hc-shout-button
|
||||
v-if="post.author"
|
||||
:disabled="isAuthor(post.author.id)"
|
||||
:count="post.shoutedCount"
|
||||
:is-shouted="post.shoutedByCurrentUser"
|
||||
:post-id="post.id"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-space>
|
||||
<!-- Comments -->
|
||||
<ds-section slot="footer">
|
||||
<hc-comment-list :post="post" />
|
||||
@ -69,6 +85,7 @@ import HcCommentForm from '~/components/comments/CommentForm/CommentForm'
|
||||
import HcCommentList from '~/components/comments/CommentList'
|
||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||
import PostQuery from '~/graphql/PostQuery.js'
|
||||
import HcEmotions from '~/components/Emotions/Emotions'
|
||||
|
||||
export default {
|
||||
name: 'PostSlug',
|
||||
@ -84,6 +101,7 @@ export default {
|
||||
ContentMenu,
|
||||
HcCommentForm,
|
||||
HcCommentList,
|
||||
HcEmotions,
|
||||
ContentViewer,
|
||||
},
|
||||
head() {
|
||||
@ -200,4 +218,9 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
.shout-button {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<h2 style="margin-bottom: .2em;">Mehr Informationen</h2>
|
||||
<p>Hier findest du weitere infos zum Thema.</p>
|
||||
<h2 style="margin-bottom: .2em;">{{ $t('post.moreInfo.title') }}</h2>
|
||||
<p>{{ $t('post.moreInfo.description') }}</p>
|
||||
<ds-space />
|
||||
<h3>
|
||||
<ds-icon name="compass" />
|
||||
Themenkategorien
|
||||
<!-- <ds-icon name="compass" /> -->
|
||||
{{ $t('post.moreInfo.titleOfCategoriesSection') }}
|
||||
</h3>
|
||||
<div class="tags">
|
||||
<ds-icon
|
||||
@ -22,8 +22,8 @@
|
||||
</div>
|
||||
<template v-if="post.tags && post.tags.length">
|
||||
<h3>
|
||||
<ds-icon name="tags" />
|
||||
Schlagwörter
|
||||
<!-- <ds-icon name="tags" /> -->
|
||||
{{ $t('post.moreInfo.titleOfHashtagsSection') }}
|
||||
</h3>
|
||||
<div class="tags">
|
||||
<ds-tag v-for="tag in post.tags" :key="tag.id">
|
||||
@ -32,7 +32,7 @@
|
||||
</ds-tag>
|
||||
</div>
|
||||
</template>
|
||||
<h3>Verwandte Beiträge</h3>
|
||||
<h3>{{ $t('post.moreInfo.titleOfRelatedContributionsSection') }}</h3>
|
||||
<ds-section style="margin: 0 -1.5rem; padding: 1.5rem;">
|
||||
<ds-flex v-if="post.relatedContributions && post.relatedContributions.length" gutter="small">
|
||||
<hc-post-card
|
||||
@ -71,7 +71,7 @@ export default {
|
||||
apollo: {
|
||||
Post: {
|
||||
query() {
|
||||
return gql(`
|
||||
return gql`
|
||||
query Post($slug: String!) {
|
||||
Post(slug: $slug) {
|
||||
id
|
||||
@ -118,7 +118,7 @@ export default {
|
||||
shoutedCount
|
||||
}
|
||||
}
|
||||
`)
|
||||
`
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import VueSweetalertIcons from 'vue-sweetalert-icons'
|
||||
|
||||
Vue.use(VueSweetalertIcons)
|
||||
1
webapp/static/img/svg/emoji/angry.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40" cy="40.1" r="40" fill="#cac9c9"/><ellipse data-name="<Pfad>" cx="38.4" cy="38.8" rx="37.4" ry="37.6" fill="#d7d8d8"/><path d="M39.8 55.6c8.8 0 13.4 7.7 13.4 11.6a.8.8 0 0 1-1 1s-5-6-12.4-6-12.4 6-12.4 6a.8.8 0 0 1-1-1c0-4 4.6-11.6 13.4-11.6z" fill="#303030"/><ellipse cx="26.4" cy="40.3" rx="4" ry="7.7" fill="#303030"/><ellipse cx="50.4" cy="39.9" rx="4" ry="7.7" fill="#303030"/><path d="M14.5 27.2s14.7-2 18.6 10c.2.3 0 .5-.4 0a22.8 22.8 0 0 0-18-6.8v-3.2zm47.5 0s-14.5-2-18.4 10c0 .3 0 .5.4 0a22.8 22.8 0 0 1 18.2-6.8v-3.2z" fill="#303030"/></svg>
|
||||
|
After Width: | Height: | Size: 658 B |
1
webapp/static/img/svg/emoji/angry_color.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><defs><radialGradient id="a" cx="37.4" cy="38.6" r="37.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ed6c70" stop-opacity=".7"/><stop offset=".3" stop-color="#ed6c70" stop-opacity=".5"/><stop offset=".8" stop-color="#ed6c70" stop-opacity=".1"/><stop offset="1" stop-color="#ed6c70" stop-opacity="0"/></radialGradient><radialGradient id="b" cx="37.4" cy="38.6" r="37.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fcea1c" stop-opacity=".2"/><stop offset=".8" stop-color="#fcea1c" stop-opacity=".1"/><stop offset="1" stop-color="#fcea1c" stop-opacity="0"/></radialGradient></defs><circle data-name="<Pfad>" cx="40" cy="40" r="40" fill="#dedc03"/><ellipse data-name="<Pfad>" cx="38.4" cy="38.7" rx="37.4" ry="37.6" fill="#fcea1c"/><ellipse data-name="<Pfad>" cx="37.4" cy="38.6" rx="37.4" ry="37.6" fill="url(#a)"/><ellipse data-name="<Pfad>" cx="37.4" cy="38.6" rx="37.4" ry="37.6" fill="url(#b)"/><ellipse cx="26.4" cy="40.2" rx="4" ry="7.7" fill="#303030"/><ellipse cx="50.4" cy="39.8" rx="4" ry="7.7" fill="#303030"/><path d="M14.5 27S29.2 25.3 33 37c.2.4 0 .6-.4 0a22.8 22.8 0 0 0-18-6.7V27zM62 27s-14.5-1.8-18.4 10c0 .4 0 .6.4 0a22.8 22.8 0 0 1 18.2-6.7V27zM39.8 55.5c8.8 0 13.4 7.7 13.4 11.6a.8.8 0 0 1-1 1s-5-6-12.4-6-12.4 6-12.4 6a.8.8 0 0 1-1-1c0-3.8 4.6-11.5 13.4-11.5z" fill="#303030"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
webapp/static/img/svg/emoji/cry.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40" cy="40" r="40" fill="#cbc9c9"/><ellipse data-name="<Pfad>" cx="38.5" cy="38.1" rx="37.4" ry="37.6" fill="#d7d7d7"/><path d="M15.8 24.5v3.2s10 1.3 12-11c0 0-6 8.8-12 7.8zm48.4 0v3.2s-10 1-12-11c0 0 6 9 12 7.8zm-23.8 27c13 0 18.4 7.7 18.4 11.6a2 2 0 0 1-2 2c-1 0-9-6-16.4-6S25 65 24 65a2 2 0 0 1-2-2c0-3.8 5.4-11.5 18.4-11.5z" fill="#303030"/><path d="M40.5 55a7.5 7.5 0 0 1 7.6 5.7 21 21 0 0 0-7.6-1.7 21.5 21.5 0 0 0-7.5 1.7 7.4 7.4 0 0 1 7.5-5.8z" fill="#ed6b70"/><path d="M35.6 42.2s-4.2-3-11.4-3-11.4 3-11.4 3S14.6 35 24.2 35s11.4 7 11.4 7zm32.4 0s-4-3-11.3-3-11.4 3-11.4 3S47 35 56.7 35 68 42 68 42z" fill="#303030"/><path d="M13.8 42.5v35c0 3.4 6 3.4 6 0v-35c0-3.7-6-3.7-6-.4v.5zm47.2 0v35c0 3.4 6 3.4 6 0v-35c0-3.7-6-3.7-6-.4v.5z" fill="#71caeb"/></svg>
|
||||
|
After Width: | Height: | Size: 866 B |
1
webapp/static/img/svg/emoji/cry_color.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40" cy="40" r="40" fill="#dedc03"/><ellipse data-name="<Pfad>" cx="38.5" cy="38.2" rx="37.4" ry="37.6" fill="#fcea1c"/><path d="M15.8 24.5v3.3s10 1.2 12-11c0 0-6 8.8-12 7.7zm48.4 0v3.3s-10 1-12-11c0 0 6 8.8 12 7.7zm-23.8 27c13 0 18.4 7.7 18.4 11.6a2 2 0 0 1-2 2c-1 0-9-6-16.4-6S25 65 24 65a2 2 0 0 1-2-2c0-3.8 5.4-11.5 18.4-11.5z" fill="#303030"/><path d="M40.5 55a7.5 7.5 0 0 1 7.6 5.8 21 21 0 0 0-7.6-1.7A21.5 21.5 0 0 0 33 61a7.4 7.4 0 0 1 7.5-5.8z" fill="#ed6c70"/><path d="M35.6 42.2s-4.2-3-11.4-3-11.4 3-11.4 3S14.6 35 24.2 35s11.4 7.2 11.4 7.2zm32.4 0s-4-3-11.3-3-11.4 3-11.4 3S47 35 56.7 35 68 42.2 68 42.2z" fill="#303030"/><path d="M13.8 42.5v35c0 3.4 6 3.4 6 0v-35c0-3.6-6-3.6-6-.3v.3zm47.2 0v35c0 3.4 6 3.4 6 0v-35c0-3.6-6-3.6-6-.3v.3z" fill="#71caeb"/></svg>
|
||||
|
After Width: | Height: | Size: 874 B |
1
webapp/static/img/svg/emoji/funny.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40.1" cy="40" r="40" fill="#cbc9c9"/><ellipse data-name="<Pfad>" cx="38.4" cy="38.6" rx="37.4" ry="37.6" fill="#d7d7d7"/><path d="M17.6 50h45.6s-3.7 19.6-22.8 19.6S17.6 50 17.6 50z" fill="#303030"/><path d="M40.4 59.8c8 0 9.8 7.8 9.8 7.8a22.6 22.6 0 0 1-9.8 2 22.7 22.7 0 0 1-9.7-2s1.5-7.8 9.7-7.8z" fill="#ed6b70"/><path d="M14.2 33.3s15-2.6 21.5 7.4A83.5 83.5 0 0 0 14.2 42c-10.3 2 4.6-4 14-3 0 0-.8-4-14-5.7zm51.6 0s-15-2.6-21.5 7.4A83.5 83.5 0 0 1 65.8 42c10.3 2-4.6-4-14-3 0 0 1-4 14-5.7z" fill="#303030"/></svg>
|
||||
|
After Width: | Height: | Size: 620 B |
1
webapp/static/img/svg/emoji/funny_color.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40.1" cy="40" r="40" fill="#dedc03"/><ellipse data-name="<Pfad>" cx="38.4" cy="38.7" rx="37.4" ry="37.6" fill="#fcea1c"/><path d="M17.6 50h45.6s-3.7 19.7-22.8 19.7S17.6 50 17.6 50z" fill="#303030"/><path d="M40.4 60c8 0 9.8 7.6 9.8 7.6a22.6 22.6 0 0 1-9.8 2 22.7 22.7 0 0 1-9.7-2s1.5-7.7 9.7-7.7z" fill="#ed6b70"/><path d="M14.2 33.4s15-2.6 21.5 7.3A83.5 83.5 0 0 0 14.2 42c-10.3 2 4.6-4 14-3 0 0-.8-4-14-5.6zm51.6 0s-15-2.6-21.5 7.3A83.5 83.5 0 0 1 65.8 42c10.3 2-4.6-4-14-3 0 0 1-4 14-5.6z" fill="#303030"/></svg>
|
||||
|
After Width: | Height: | Size: 618 B |
1
webapp/static/img/svg/emoji/happy.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40" cy="40" r="40" fill="#cbc9c9"/><ellipse data-name="<Pfad>" cx="38.4" cy="38.6" rx="37.4" ry="37.6" fill="#d7d7d7"/><ellipse cx="55.8" cy="39" rx="4" ry="7.7" fill="#303030"/><ellipse cx="24.1" cy="39" rx="4" ry="7.7" fill="#303030"/><ellipse cx="24.1" cy="45.9" rx="5.7" ry=".8" fill="#303030"/><ellipse cx="55.8" cy="45.9" rx="5.7" ry=".8" fill="#303030"/><path d="M17.5 55s8.3 4 22.8 4S63 55 63 55s-3.6 14.6-22.7 14.6S17.5 55 17.5 55z" fill="#303030"/><path d="M40.3 62.8c8 0 9.4 5.4 9.2 5.5a28.8 28.8 0 0 1-9.2 1.3 28.8 28.8 0 0 1-9.2-1.3s1-5.5 9.3-5.5z" fill="#ed6b70"/></svg>
|
||||
|
After Width: | Height: | Size: 687 B |
1
webapp/static/img/svg/emoji/happy_color.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><circle data-name="<Pfad>" cx="40" cy="40" r="40" fill="#dedc03"/><ellipse data-name="<Pfad>" cx="38.4" cy="38.7" rx="37.4" ry="37.6" fill="#fcea1c"/><ellipse cx="55.8" cy="39" rx="4" ry="7.7" fill="#303030"/><ellipse cx="24.1" cy="39" rx="4" ry="7.7" fill="#303030"/><ellipse cx="24.1" cy="45.9" rx="5.7" ry=".8" fill="#303030"/><ellipse cx="55.8" cy="45.9" rx="5.7" ry=".8" fill="#303030"/><path d="M17.5 55s8.3 4 22.8 4S63 55 63 55s-3.6 14.7-22.7 14.7S17.5 55 17.5 55z" fill="#303030"/><path d="M40.3 63c8 0 9.4 5.3 9.2 5.3a28.8 28.8 0 0 1-9.2 1.4 28.8 28.8 0 0 1-9.2-1.4s1-5.4 9.3-5.4z" fill="#ed6b70"/></svg>
|
||||
|
After Width: | Height: | Size: 685 B |
1
webapp/static/img/svg/emoji/surprised.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="new" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><style></style><circle id="XMLID_40_" cx="40" cy="40" r="40" fill="#cac9c9"/><ellipse id="XMLID_39_" cx="38.4" cy="38.7" rx="37.4" ry="37.6" fill="#d7d8d8"/><ellipse id="Oval" fill="#303030" cx="55.8" cy="39" rx="4" ry="7.7"/><ellipse id="Oval" fill="#303030" cx="24.1" cy="39" rx="4" ry="7.7"/><path d="M45 61.4c0-2.3-1.2-4.4-6-4.4-5 0-6 2.2-6 4.4 0 2.3 1 6.6 6 6.6 4.8 0 6-4.4 6-6.6zM51.1 19L50 16s8.9-4.6 15 6c0 0-8.6-6.1-13.9-3zM26.4 18.4l.6-3.1S17 12.6 13 24c0 0 7.5-7.6 13.4-5.6z" id="Path" fill="#303030"/></svg>
|
||||
|
After Width: | Height: | Size: 588 B |
1
webapp/static/img/svg/emoji/surprised_color.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="new" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><style></style><circle id="XMLID_40_" cx="40" cy="40" r="40" fill="#dedc03"/><ellipse id="XMLID_39_" cx="38.4" cy="38.7" rx="37.4" ry="37.6" fill="#fcea1c"/><ellipse id="Oval" fill="#303030" cx="55.8" cy="39" rx="4" ry="7.7"/><ellipse id="Oval" fill="#303030" cx="24.1" cy="39" rx="4" ry="7.7"/><path d="M45 61.4c0-2.3-1.2-4.4-6-4.4-5 0-6 2.2-6 4.4 0 2.3 1 6.6 6 6.6 4.8 0 6-4.4 6-6.6zM51.1 19L50 16s8.9-4.6 15 6c0 0-8.6-6.1-13.9-3zM26.4 18.4l.6-3.1S17 12.6 13 24c0 0 7.5-7.6 13.4-5.6z" id="Path" fill="#303030"/></svg>
|
||||
|
After Width: | Height: | Size: 588 B |
@ -3,6 +3,11 @@ import gql from 'graphql-tag'
|
||||
export const state = () => {
|
||||
return {
|
||||
posts: [],
|
||||
filteredByUsersFollowed: false,
|
||||
filteredByCategories: false,
|
||||
usersFollowedFilter: {},
|
||||
categoriesFilter: {},
|
||||
selectedCategoryIds: [],
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +15,51 @@ export const mutations = {
|
||||
SET_POSTS(state, posts) {
|
||||
state.posts = posts || null
|
||||
},
|
||||
SET_FILTERED_BY_FOLLOWERS(state, boolean) {
|
||||
state.filteredByUsersFollowed = boolean || null
|
||||
},
|
||||
SET_FILTERED_BY_CATEGORIES(state, boolean) {
|
||||
state.filteredByCategories = boolean || null
|
||||
},
|
||||
SET_USERS_FOLLOWED_FILTER(state, filter) {
|
||||
state.usersFollowedFilter = filter || null
|
||||
},
|
||||
SET_CATEGORIES_FILTER(state, filter) {
|
||||
state.categoriesFilter = filter || null
|
||||
},
|
||||
SET_SELECTED_CATEGORY_IDS(state, categoryId) {
|
||||
if (!categoryId) {
|
||||
state.selectedCategoryIds = []
|
||||
} else {
|
||||
const index = state.selectedCategoryIds.indexOf(categoryId)
|
||||
if (index > -1) {
|
||||
state.selectedCategoryIds.splice(index, 1)
|
||||
} else {
|
||||
state.selectedCategoryIds.push(categoryId)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
posts(state) {
|
||||
return state.posts || []
|
||||
},
|
||||
filteredByUsersFollowed(state) {
|
||||
return state.filteredByUsersFollowed || false
|
||||
},
|
||||
filteredByCategories(state) {
|
||||
return state.filteredByCategories || false
|
||||
},
|
||||
usersFollowedFilter(state) {
|
||||
return state.usersFollowedFilter || {}
|
||||
},
|
||||
categoriesFilter(state) {
|
||||
return state.categoriesFilter || {}
|
||||
},
|
||||
selectedCategoryIds(state) {
|
||||
return state.selectedCategoryIds || []
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
@ -24,7 +68,7 @@ export const actions = {
|
||||
const {
|
||||
data: { Post },
|
||||
} = await client.query({
|
||||
query: gql(`
|
||||
query: gql`
|
||||
query Post($filter: _PostFilter, $first: Int, $offset: Int) {
|
||||
Post(filter: $filter, first: $first, offset: $offset) {
|
||||
id
|
||||
@ -63,7 +107,7 @@ export const actions = {
|
||||
}
|
||||
shoutedCount
|
||||
}
|
||||
}`),
|
||||
}`,
|
||||
variables: {
|
||||
filter,
|
||||
first: 12,
|
||||
|
||||
50
webapp/store/postsFilter.js
Normal file
@ -0,0 +1,50 @@
|
||||
import get from 'lodash/get'
|
||||
import update from 'lodash/update'
|
||||
import xor from 'lodash/xor'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import clone from 'lodash/clone'
|
||||
|
||||
export const state = () => {
|
||||
return {
|
||||
filter: {},
|
||||
}
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
TOGGLE_FILTER_BY_FOLLOWED(state, currentUserId) {
|
||||
const filter = clone(state.filter)
|
||||
const id = get(filter, 'author.followedBy_some.id')
|
||||
if (id) {
|
||||
delete filter.author
|
||||
state.filter = filter
|
||||
} else {
|
||||
state.filter = {
|
||||
...filter,
|
||||
author: { followedBy_some: { id: currentUserId } },
|
||||
}
|
||||
}
|
||||
},
|
||||
RESET_CATEGORIES(state) {
|
||||
const filter = clone(state.filter)
|
||||
delete filter.categories_some
|
||||
state.filter = filter
|
||||
},
|
||||
TOGGLE_CATEGORY(state, categoryId) {
|
||||
const filter = clone(state.filter)
|
||||
update(filter, 'categories_some.id_in', categoryIds => xor(categoryIds, [categoryId]))
|
||||
if (isEmpty(get(filter, 'categories_some.id_in'))) delete filter.categories_some
|
||||
state.filter = filter
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
postsFilter(state) {
|
||||
return state.filter
|
||||
},
|
||||
filteredCategoryIds(state) {
|
||||
return get(state.filter, 'categories_some.id_in') || []
|
||||
},
|
||||
filteredByUsersFollowed(state) {
|
||||
return !!get(state.filter, 'author.followedBy_some.id')
|
||||
},
|
||||
}
|
||||
126
webapp/store/postsFilter.spec.js
Normal file
@ -0,0 +1,126 @@
|
||||
import { getters, mutations } from './postsFilter.js'
|
||||
|
||||
let state
|
||||
let testAction
|
||||
|
||||
describe('getters', () => {
|
||||
describe('filteredCategoryIds', () => {
|
||||
it('returns category ids if filter is set', () => {
|
||||
state = { filter: { categories_some: { id_in: [24] } } }
|
||||
expect(getters.filteredCategoryIds(state)).toEqual([24])
|
||||
})
|
||||
|
||||
it('returns empty array if filter is not set', () => {
|
||||
state = { filter: { author: { followedBy_some: { id: 7 } } } }
|
||||
expect(getters.filteredCategoryIds(state)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('postsFilter', () => {
|
||||
it('returns filter', () => {
|
||||
state = { filter: { author: { followedBy_some: { id: 7 } } } }
|
||||
expect(getters.postsFilter(state)).toEqual({ author: { followedBy_some: { id: 7 } } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('filteredByUsersFollowed', () => {
|
||||
it('returns true if filter is set', () => {
|
||||
state = { filter: { author: { followedBy_some: { id: 7 } } } }
|
||||
expect(getters.filteredByUsersFollowed(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false if filter is not set', () => {
|
||||
state = { filter: { categories_some: { id_in: [23] } } }
|
||||
expect(getters.filteredByUsersFollowed(state)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutations', () => {
|
||||
describe('RESET_CATEGORIES', () => {
|
||||
beforeEach(() => {
|
||||
testAction = categoryId => {
|
||||
mutations.RESET_CATEGORIES(state, categoryId)
|
||||
return getters.postsFilter(state)
|
||||
}
|
||||
})
|
||||
it('resets the categories filter', () => {
|
||||
state = {
|
||||
filter: {
|
||||
author: { followedBy_some: { id: 7 } },
|
||||
categories_some: { id_in: [23] },
|
||||
},
|
||||
}
|
||||
expect(testAction(23)).toEqual({ author: { followedBy_some: { id: 7 } } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('TOGGLE_CATEGORY', () => {
|
||||
beforeEach(() => {
|
||||
testAction = categoryId => {
|
||||
mutations.TOGGLE_CATEGORY(state, categoryId)
|
||||
return getters.postsFilter(state)
|
||||
}
|
||||
})
|
||||
|
||||
it('creates category filter if empty', () => {
|
||||
state = { filter: {} }
|
||||
expect(testAction(23)).toEqual({ categories_some: { id_in: [23] } })
|
||||
})
|
||||
|
||||
it('adds category id not present', () => {
|
||||
state = { filter: { categories_some: { id_in: [24] } } }
|
||||
expect(testAction(23)).toEqual({ categories_some: { id_in: [24, 23] } })
|
||||
})
|
||||
|
||||
it('removes category id if present', () => {
|
||||
state = { filter: { categories_some: { id_in: [23, 24] } } }
|
||||
const result = testAction(23)
|
||||
expect(result).toEqual({ categories_some: { id_in: [24] } })
|
||||
})
|
||||
|
||||
it('removes category filter if empty', () => {
|
||||
state = { filter: { categories_some: { id_in: [23] } } }
|
||||
expect(testAction(23)).toEqual({})
|
||||
})
|
||||
|
||||
it('does not get in the way of other filters', () => {
|
||||
state = {
|
||||
filter: {
|
||||
author: { followedBy_some: { id: 7 } },
|
||||
categories_some: { id_in: [23] },
|
||||
},
|
||||
}
|
||||
expect(testAction(23)).toEqual({ author: { followedBy_some: { id: 7 } } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('TOGGLE_FILTER_BY_FOLLOWED', () => {
|
||||
beforeEach(() => {
|
||||
testAction = userId => {
|
||||
mutations.TOGGLE_FILTER_BY_FOLLOWED(state, userId)
|
||||
return getters.postsFilter(state)
|
||||
}
|
||||
})
|
||||
|
||||
describe('given empty filter', () => {
|
||||
beforeEach(() => {
|
||||
state = { filter: {} }
|
||||
})
|
||||
|
||||
it('attaches the id of the current user to the filter object', () => {
|
||||
expect(testAction(4711)).toEqual({ author: { followedBy_some: { id: 4711 } } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('already filtered', () => {
|
||||
beforeEach(() => {
|
||||
state = { filter: { author: { followedBy_some: { id: 4711 } } } }
|
||||
})
|
||||
|
||||
it('remove the id of the current user from the filter object', () => {
|
||||
expect(testAction(4711)).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
381
webapp/yarn.lock
@ -1645,17 +1645,17 @@
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
|
||||
|
||||
"@storybook/addon-a11y@^5.1.9":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-5.1.10.tgz#0bf37a8e2827cdaa199b293b14e71e8434246591"
|
||||
integrity sha512-YiRj/8IQ5zq/I+x+aRyfS5PP9nTfuTU7O90+WtNomqCJPMBOrR3BYsEcl510jOy2iwhQwh76MFT5s1tKpMclAA==
|
||||
"@storybook/addon-a11y@^5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-5.1.11.tgz#170e0f406b6c0a07fd1e28e6323a694df3f087fc"
|
||||
integrity sha512-kMBDPl0DslamNCtOGGqGlTjRDTxmEcu8JMTaZSa4GnTwzfN+ugb+aUEkbKl3VjMW7GsdpgizMTWBtgf6SwNj8w==
|
||||
dependencies:
|
||||
"@storybook/addons" "5.1.10"
|
||||
"@storybook/api" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/components" "5.1.10"
|
||||
"@storybook/core-events" "5.1.10"
|
||||
"@storybook/theming" "5.1.10"
|
||||
"@storybook/addons" "5.1.11"
|
||||
"@storybook/api" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
"@storybook/components" "5.1.11"
|
||||
"@storybook/core-events" "5.1.11"
|
||||
"@storybook/theming" "5.1.11"
|
||||
axe-core "^3.2.2"
|
||||
common-tags "^1.8.0"
|
||||
core-js "^3.0.1"
|
||||
@ -1668,16 +1668,16 @@
|
||||
redux "^4.0.1"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/addon-actions@^5.1.9":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.10.tgz#8ed4272a6afc68f4a30372da2eeff414f0fe6ecd"
|
||||
integrity sha512-njl2AHBGi27NvisOB8LFnWH/3RcyJT/CW7tl1cvV2j5FH2oBjq5MsjxKyJIcKwC677k1Wr8G8fw/zSEHrPpmgA==
|
||||
"@storybook/addon-actions@^5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.11.tgz#ebc299b9dfe476b5c65eb5d148c4b064f682ca08"
|
||||
integrity sha512-Fp4b8cBYrl9zudvamVYTxE1XK2tzg91hgBDoVxIbDvSMZ2aQXSq8B5OFS4eSdvg+ldEOBbvIgUNS1NIw+FGntQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "5.1.10"
|
||||
"@storybook/api" "5.1.10"
|
||||
"@storybook/components" "5.1.10"
|
||||
"@storybook/core-events" "5.1.10"
|
||||
"@storybook/theming" "5.1.10"
|
||||
"@storybook/addons" "5.1.11"
|
||||
"@storybook/api" "5.1.11"
|
||||
"@storybook/components" "5.1.11"
|
||||
"@storybook/core-events" "5.1.11"
|
||||
"@storybook/theming" "5.1.11"
|
||||
core-js "^3.0.1"
|
||||
fast-deep-equal "^2.0.1"
|
||||
global "^4.3.2"
|
||||
@ -1688,28 +1688,28 @@
|
||||
react-inspector "^3.0.2"
|
||||
uuid "^3.3.2"
|
||||
|
||||
"@storybook/addons@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.1.10.tgz#2d8d8ca20b6d9b4652744f5fc00ead483f705435"
|
||||
integrity sha512-M9b2PCp9RZxDC6wL7vVt2SCKCGXrrEAOsdpMvU569yB1zoUPEiiqElVDwb91O2eAGPnmd2yjImp90kOpKUW0EA==
|
||||
"@storybook/addons@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.1.11.tgz#27f9cfed8d7f7c8a3fc341cdba3b0bdf608f02aa"
|
||||
integrity sha512-714Xg6pX4rjDY1urL94w4oOxIiK6jCFSp4oKvqLj7dli5CG7d34Yt9joyTgOb2pkbrgmbMWAZJq0L0iOjHzpzw==
|
||||
dependencies:
|
||||
"@storybook/api" "5.1.10"
|
||||
"@storybook/channels" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/api" "5.1.11"
|
||||
"@storybook/channels" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/api@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.1.10.tgz#5eeb5d9a7c268e5c89bd40c9a80293a7c72343b8"
|
||||
integrity sha512-YeZe/71zLMmgT95IMAEZOc9AwL6Y23mWvkZMwFbkokxS9+bU/qmVlQ0B9c3JBzO3OSs7sXaRqyP1o3QkQgVsiw==
|
||||
"@storybook/api@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.1.11.tgz#71ef00285cd8602aad24cdb26c60c5d3c76631e5"
|
||||
integrity sha512-zzPZM6W67D4YKCbUN4RhC/w+/CtnH/hFbSh/QUBdwXFB1aLh2qA1UTyB8i6m6OA6JgVHBqEkl10KhmeILLv/eA==
|
||||
dependencies:
|
||||
"@storybook/channels" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/core-events" "5.1.10"
|
||||
"@storybook/router" "5.1.10"
|
||||
"@storybook/theming" "5.1.10"
|
||||
"@storybook/channels" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
"@storybook/core-events" "5.1.11"
|
||||
"@storybook/router" "5.1.11"
|
||||
"@storybook/theming" "5.1.11"
|
||||
core-js "^3.0.1"
|
||||
fast-deep-equal "^2.0.1"
|
||||
global "^4.3.2"
|
||||
@ -1723,33 +1723,33 @@
|
||||
telejson "^2.2.1"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/channel-postmessage@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.1.10.tgz#e0a58461d56ef20a87d8bc4df1067e7afc76950e"
|
||||
integrity sha512-kQZIwltN2cWDXluhCfdModFDK1LHV9ZhNQ1b/uD9vn1c65rQ9u7r4lRajCfS0X1dmAWqz48cBcEurAubNgmswg==
|
||||
"@storybook/channel-postmessage@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.1.11.tgz#e75ab7d59ba19476eb631cdb69ee713c3b956c2b"
|
||||
integrity sha512-S7Uq7+c9kOJ9BB4H9Uro2+dVhqoMchYCipQzAkD4jIIwK99RNzGdAaRipDC1k0k/C+v2SOa+D5xBbb3XVYPSrg==
|
||||
dependencies:
|
||||
"@storybook/channels" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/channels" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
telejson "^2.2.1"
|
||||
|
||||
"@storybook/channels@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.1.10.tgz#04fd35c05032c675f7816ea1ca873c1a0415c6d9"
|
||||
integrity sha512-w7n/bV1BLu51KI1eLc75lN9H1ssBc3PZMXk88GkMiKyBVRzPlJA5ixnzH86qwYGReE0dhRpsgHXZ5XmoKaVmPA==
|
||||
"@storybook/channels@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.1.11.tgz#77ddf9d777891f975ac10095772c840fed4c4620"
|
||||
integrity sha512-MlrjVGNvYOnDvv2JDRhr4wikbnZ8HCFCpVsFqKPFxj7I3OYBR417RvFkydX3Rtx4kwB9rmZEgLhfAfsSytkALg==
|
||||
dependencies:
|
||||
core-js "^3.0.1"
|
||||
|
||||
"@storybook/client-api@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.1.10.tgz#a10f028f2d33d044e5c3b3daea5d8375323e6a66"
|
||||
integrity sha512-v2PqiNUhwDlVDLYL94f6LFjdYMToTpuwWh9aeqzt/4PAJUnIcA+2P8+qXiYdJTqQy/u7P72HFMlc9Ru4tl3QFg==
|
||||
"@storybook/client-api@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.1.11.tgz#30d82c09c6c40aa70d932e77b1d1e65526bddc0c"
|
||||
integrity sha512-znzSxZ1ZCqtEKrFoW7xT8iBbdiAXaQ8RNxQFKHuYPqWX+RLol6S3duEOxu491X2SzUg0StUmrX5qL9Rnth8dRQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/core-events" "5.1.10"
|
||||
"@storybook/router" "5.1.10"
|
||||
"@storybook/addons" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
"@storybook/core-events" "5.1.11"
|
||||
"@storybook/router" "5.1.11"
|
||||
common-tags "^1.8.0"
|
||||
core-js "^3.0.1"
|
||||
eventemitter3 "^3.1.0"
|
||||
@ -1759,20 +1759,20 @@
|
||||
memoizerific "^1.11.3"
|
||||
qs "^6.6.0"
|
||||
|
||||
"@storybook/client-logger@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.1.10.tgz#f83a8717924dd222e0a6df82ae74701f27e0bb35"
|
||||
integrity sha512-vB1NoFWRTgcERwodhbgoDwI00eqU8++nXI7GhMS1CY8haZaSp3gyKfHRWyfH+M+YjQuGBRUcvIk4gK6OtSrDOw==
|
||||
"@storybook/client-logger@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.1.11.tgz#9509af3021b7a9977f9dba1f2ff038fd3c994437"
|
||||
integrity sha512-je4To+9zD3SEJsKe9R4u15N4bdXFBR7pdBToaRIur+XSvvShLFehZGseQi+4uPAj8vyG34quGTCeUC/BKY0LwQ==
|
||||
dependencies:
|
||||
core-js "^3.0.1"
|
||||
|
||||
"@storybook/components@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.1.10.tgz#4b6436f0b5bb2483fb231bee263d173a9ed7d241"
|
||||
integrity sha512-QUQeeQp1xNWiL4VlxFAea0kqn2zvBfmfPlUddOFO9lBhT6pVy0xYPjXjbTVWjVcYzZpyUNWw5GplqrR5jhlaCA==
|
||||
"@storybook/components@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.1.11.tgz#da253af0a8cb1b063c5c2e8016c4540c983f717d"
|
||||
integrity sha512-EQgD7HL2CWnnY968KrwUSU2dtKFGTGRJVc4vwphYEeZwAI0lX6qbTMuwEP22hDZ2OSRBxcvcXT8cvduDlZlFng==
|
||||
dependencies:
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/theming" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
"@storybook/theming" "5.1.11"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
markdown-to-jsx "^6.9.1"
|
||||
@ -1790,32 +1790,32 @@
|
||||
recompose "^0.30.0"
|
||||
simplebar-react "^1.0.0-alpha.6"
|
||||
|
||||
"@storybook/core-events@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.1.10.tgz#5aed88c572036b6bd6dfff28976ee96e6e175d7a"
|
||||
integrity sha512-Lvu/rNcgS+XCkQKSGdNpUSWjpFF9AOSHPXsvkwHbRwJYdMDn3FznlXfDUiubOWtsziXHB6vl3wkKDlH+ckb32Q==
|
||||
"@storybook/core-events@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.1.11.tgz#9d00503a936d30398f7a64336eb956303d053765"
|
||||
integrity sha512-m+yIFRdB47+IPBFBGS2OUXrSLkoz5iAXvb3c0lGAePf5wSR+o/Ni/9VD5l6xBf+InxHLSc9gcDEJehrT0fJAaQ==
|
||||
dependencies:
|
||||
core-js "^3.0.1"
|
||||
|
||||
"@storybook/core@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.1.10.tgz#53d23d07716aa2721e1572d44a7f05967d7da39e"
|
||||
integrity sha512-zkNjufOFrLpFpmr73F/gaJh0W0vWqXIo5zrKvQt1LqmMeCU/v8MstHi4XidlK43UpeogfaXl5tjNCQDO/bd0Dw==
|
||||
"@storybook/core@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.1.11.tgz#d7c4b14b02f74c183ab5baffe9b3e5ec8289b320"
|
||||
integrity sha512-LkSoAJlLEtrzFcoINX3dz4oT6xUPEHEp2/WAXLqUFeCnzJHAxIsRvbVxB49Kh/2TrgDFZpL9Or8XXMzZtE6KYw==
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-class-properties" "^7.3.3"
|
||||
"@babel/plugin-proposal-object-rest-spread" "^7.3.2"
|
||||
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
|
||||
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
|
||||
"@babel/preset-env" "^7.4.5"
|
||||
"@storybook/addons" "5.1.10"
|
||||
"@storybook/channel-postmessage" "5.1.10"
|
||||
"@storybook/client-api" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/core-events" "5.1.10"
|
||||
"@storybook/node-logger" "5.1.10"
|
||||
"@storybook/router" "5.1.10"
|
||||
"@storybook/theming" "5.1.10"
|
||||
"@storybook/ui" "5.1.10"
|
||||
"@storybook/addons" "5.1.11"
|
||||
"@storybook/channel-postmessage" "5.1.11"
|
||||
"@storybook/client-api" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
"@storybook/core-events" "5.1.11"
|
||||
"@storybook/node-logger" "5.1.11"
|
||||
"@storybook/router" "5.1.11"
|
||||
"@storybook/theming" "5.1.11"
|
||||
"@storybook/ui" "5.1.11"
|
||||
airbnb-js-shims "^1 || ^2"
|
||||
autoprefixer "^9.4.9"
|
||||
babel-plugin-add-react-displayname "^0.0.5"
|
||||
@ -1863,16 +1863,17 @@
|
||||
shelljs "^0.8.3"
|
||||
style-loader "^0.23.1"
|
||||
terser-webpack-plugin "^1.2.4"
|
||||
unfetch "^4.1.0"
|
||||
url-loader "^1.1.2"
|
||||
util-deprecate "^1.0.2"
|
||||
webpack "^4.33.0"
|
||||
webpack-dev-middleware "^3.7.0"
|
||||
webpack-hot-middleware "^2.25.0"
|
||||
|
||||
"@storybook/node-logger@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.1.10.tgz#92c80b46177687cd8fda1f93a055c22711984154"
|
||||
integrity sha512-Z4UKh7QBOboQhUF5S/dKOx3OWWCNZGwYu8HZa/O+P68+XnQDhuZCYwqWG49xFhZd0Jb0W9gdUL2mWJw5POG9PA==
|
||||
"@storybook/node-logger@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.1.11.tgz#bbf5ad0d148e6c9a9b7cf6f62ad4df4e9fa19e5d"
|
||||
integrity sha512-LG0KM4lzb9LEffcO3Ps9FcHHsVgQUc/oG+kz3p0u9fljFoL3cJHF1Mb4o+HrSydtdWZs/spwZ/BLEo5n/AByDw==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
core-js "^3.0.1"
|
||||
@ -1880,10 +1881,10 @@
|
||||
pretty-hrtime "^1.0.3"
|
||||
regenerator-runtime "^0.12.1"
|
||||
|
||||
"@storybook/router@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.1.10.tgz#d3cffd3f1105eb665882f389746ccabbb98c3c16"
|
||||
integrity sha512-BdG6/essPZFHCP2ewCG0gYFQfmuuTSHXAB5fd/rwxLSYj1IzNznC5OxkvnSaTr4rgoxxaW/z1hbN1NuA0ivlFA==
|
||||
"@storybook/router@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.1.11.tgz#75089e9e623482e52ed894c3f0cb0fc6a5372da9"
|
||||
integrity sha512-Xt7R1IOWLlIxis6VKV9G8F+e/G4G8ng1zXCqoDq+/RlWzlQJ5ccO4bUm2/XGS1rEgY4agMzmzjum18HoATpLGA==
|
||||
dependencies:
|
||||
"@reach/router" "^1.2.1"
|
||||
core-js "^3.0.1"
|
||||
@ -1891,14 +1892,14 @@
|
||||
memoizerific "^1.11.3"
|
||||
qs "^6.6.0"
|
||||
|
||||
"@storybook/theming@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.10.tgz#f9bd519cdf9cccf730656e3f5fd56a339dd07c9f"
|
||||
integrity sha512-5cN1lmdVUwAR8U3T49Lfb8JW5RBvxBSPGZpUmbLGz1zi0tWBJgYXoGtw4RbTBjV9kCQOXkHGH12AsdDxHh931w==
|
||||
"@storybook/theming@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.11.tgz#0d1af46535f2e601293c999a314905069a93ec3b"
|
||||
integrity sha512-PtRPfiAWx5pQbTm45yyPB+CuW/vyDmcmNOt+xnDzK52omeWaSD7XK2RfadN3u4QXCgha7zs35Ppx1htJio2NRA==
|
||||
dependencies:
|
||||
"@emotion/core" "^10.0.9"
|
||||
"@emotion/styled" "^10.0.7"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
common-tags "^1.8.0"
|
||||
core-js "^3.0.1"
|
||||
deep-object-diff "^1.1.0"
|
||||
@ -1909,19 +1910,19 @@
|
||||
prop-types "^15.7.2"
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
"@storybook/ui@5.1.10":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.1.10.tgz#4262b1b09efa43d125d694452ae879b89071edd1"
|
||||
integrity sha512-ezkoVtzoKh93z2wzkqVIqyrIzTkj8tizgAkoPa7mUAbLCxu6LErHITODQoyEiJWI4Epy3yU9GYXFWwT71hdwsA==
|
||||
"@storybook/ui@5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.1.11.tgz#02246f7656f644a36908430de12abbdf4e2a8a72"
|
||||
integrity sha512-mopuFSwtodvH4HRdaSBlgYxzYca1qyvzZ0BxOPocXhiFfFR+V9NyNJqKKRA3vinWuuZWpYcnPTu3h8skmjMirg==
|
||||
dependencies:
|
||||
"@storybook/addons" "5.1.10"
|
||||
"@storybook/api" "5.1.10"
|
||||
"@storybook/channels" "5.1.10"
|
||||
"@storybook/client-logger" "5.1.10"
|
||||
"@storybook/components" "5.1.10"
|
||||
"@storybook/core-events" "5.1.10"
|
||||
"@storybook/router" "5.1.10"
|
||||
"@storybook/theming" "5.1.10"
|
||||
"@storybook/addons" "5.1.11"
|
||||
"@storybook/api" "5.1.11"
|
||||
"@storybook/channels" "5.1.11"
|
||||
"@storybook/client-logger" "5.1.11"
|
||||
"@storybook/components" "5.1.11"
|
||||
"@storybook/core-events" "5.1.11"
|
||||
"@storybook/router" "5.1.11"
|
||||
"@storybook/theming" "5.1.11"
|
||||
copy-to-clipboard "^3.0.8"
|
||||
core-js "^3.0.1"
|
||||
core-js-pure "^3.0.1"
|
||||
@ -1949,12 +1950,12 @@
|
||||
telejson "^2.2.1"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/vue@~5.1.9":
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/vue/-/vue-5.1.10.tgz#37916c93faf2eca21497b359748109727ccf3216"
|
||||
integrity sha512-UeRbQ5bOWUTx5oBMfPf+ZtP5E5X74nFFhrkg0yNakohW6pLuTVoci/G8hDJ4wsjT7PgNjoE1/dggf4JKCU9tjA==
|
||||
"@storybook/vue@~5.1.11":
|
||||
version "5.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/vue/-/vue-5.1.11.tgz#440c260afa46247e80431470ee50e2725e06b579"
|
||||
integrity sha512-hhCBfYyoBHehZf2P4BO9C1CuvY9m9GfiaWwqKl8WTGSdy8H6no5ZCRkG2SskS/h+mzJ1+WIGqnHkm0iIDz6KSg==
|
||||
dependencies:
|
||||
"@storybook/core" "5.1.10"
|
||||
"@storybook/core" "5.1.11"
|
||||
common-tags "^1.8.0"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
@ -2777,14 +2778,14 @@ apollo-cache-control@0.8.1:
|
||||
apollo-server-env "2.4.1"
|
||||
graphql-extensions "0.8.1"
|
||||
|
||||
apollo-cache-inmemory@^1.6.2, apollo-cache-inmemory@~1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.2.tgz#bbf2e4e1eacdf82b2d526f5c2f3b37e5acee3c5e"
|
||||
integrity sha512-AyCl3PGFv5Qv1w4N9vlg63GBPHXgMCekZy5mhlS042ji0GW84uTySX+r3F61ZX3+KM1vA4m9hQyctrEGiv5XjQ==
|
||||
apollo-cache-inmemory@^1.6.2, apollo-cache-inmemory@~1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d"
|
||||
integrity sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg==
|
||||
dependencies:
|
||||
apollo-cache "^1.3.2"
|
||||
apollo-utilities "^1.3.2"
|
||||
optimism "^0.9.0"
|
||||
optimism "^0.10.0"
|
||||
ts-invariant "^0.4.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
@ -2796,10 +2797,10 @@ apollo-cache@1.3.2, apollo-cache@^1.3.2:
|
||||
apollo-utilities "^1.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-client@^2.6.3, apollo-client@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.3.tgz#9bb2d42fb59f1572e51417f341c5f743798d22db"
|
||||
integrity sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==
|
||||
apollo-client@^2.6.3, apollo-client@~2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.4.tgz#872c32927263a0d34655c5ef8a8949fbb20b6140"
|
||||
integrity sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ==
|
||||
dependencies:
|
||||
"@types/zen-observable" "^0.8.0"
|
||||
apollo-cache "1.3.2"
|
||||
@ -3014,17 +3015,7 @@ apollo-link-ws@^1.0.18:
|
||||
apollo-link "^1.2.12"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link@^1.0.0, apollo-link@^1.2.1, apollo-link@^1.2.11, apollo-link@^1.2.3:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
|
||||
integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
|
||||
dependencies:
|
||||
apollo-utilities "^1.2.1"
|
||||
ts-invariant "^0.3.2"
|
||||
tslib "^1.9.3"
|
||||
zen-observable-ts "^0.8.18"
|
||||
|
||||
apollo-link@^1.2.12:
|
||||
apollo-link@^1.0.0, apollo-link@^1.2.1, apollo-link@^1.2.11, apollo-link@^1.2.12, apollo-link@^1.2.3:
|
||||
version "1.2.12"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.12.tgz#014b514fba95f1945c38ad4c216f31bcfee68429"
|
||||
integrity sha512-fsgIAXPKThyMVEMWQsUN22AoQI+J/pVXcjRGAShtk97h7D8O+SPskFinCGEkxPeQpE83uKaqafB2IyWdjN+J3Q==
|
||||
@ -4764,15 +4755,14 @@ cliui@^4.0.0:
|
||||
strip-ansi "^4.0.0"
|
||||
wrap-ansi "^2.0.0"
|
||||
|
||||
clone-deep@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
|
||||
integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
|
||||
clone-deep@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
|
||||
dependencies:
|
||||
for-own "^1.0.0"
|
||||
is-plain-object "^2.0.4"
|
||||
kind-of "^6.0.0"
|
||||
shallow-clone "^1.0.0"
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clone-response@1.0.2:
|
||||
version "1.0.2"
|
||||
@ -5113,12 +5103,7 @@ core-js@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
||||
|
||||
core-js@^2.4.0, core-js@^2.6.5:
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
|
||||
integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
|
||||
|
||||
core-js@^2.5.0, core-js@~2.6.9:
|
||||
core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5, core-js@~2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
|
||||
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
|
||||
@ -6219,10 +6204,10 @@ eslint-plugin-import@~2.18.2:
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jest@~22.14.1:
|
||||
version "22.14.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.14.1.tgz#32287dade9bc0a1920c61e25a71cf11363d78015"
|
||||
integrity sha512-mpLjhADl+HjagrlaGNx95HIji089S18DhnU/Ee8P8VP+dhEnuEzb43BXEaRmDgQ7BiSUPcSCvt1ydtgPkjOF/Q==
|
||||
eslint-plugin-jest@~22.15.1:
|
||||
version "22.15.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.15.1.tgz#54c4a752a44c4bc5a564ecc22b32e1cd16a2961a"
|
||||
integrity sha512-CWq/RR/3tLaKFB+FZcCJwU9hH5q/bKeO3rFP8G07+q7hcDCFNqpvdphVbEbGE6o6qo1UbciEev4ejUWv7brUhw==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^1.13.0"
|
||||
|
||||
@ -6918,23 +6903,11 @@ follow-redirects@^1.0.0:
|
||||
dependencies:
|
||||
debug "^3.2.6"
|
||||
|
||||
for-in@^0.1.3:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
|
||||
integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
|
||||
|
||||
for-own@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
|
||||
integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=
|
||||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
@ -9574,11 +9547,6 @@ lodash.sortby@^4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash.tail@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
|
||||
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
|
||||
|
||||
lodash.template@^4.2.4, lodash.template@^4.4.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
|
||||
@ -9614,12 +9582,7 @@ lodash.uniqueid@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniqueid/-/lodash.uniqueid-4.0.1.tgz#3268f26a7c88e4f4b1758d679271814e31fa5b26"
|
||||
integrity sha1-MmjyanyI5PSxdY1nknGBTjH6WyY=
|
||||
|
||||
lodash@4.x, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
|
||||
version "4.17.14"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
|
||||
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
|
||||
|
||||
lodash@^4.17.12:
|
||||
lodash@4.x, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
@ -10046,14 +10009,6 @@ mixin-deep@^1.2.0:
|
||||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
mixin-object@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
|
||||
integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
|
||||
dependencies:
|
||||
for-in "^0.1.3"
|
||||
is-extendable "^0.1.1"
|
||||
|
||||
mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
@ -10149,12 +10104,7 @@ negotiator@0.6.2:
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
|
||||
neo-async@^2.5.0, neo-async@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
|
||||
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
|
||||
|
||||
neo-async@^2.6.1:
|
||||
neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||
@ -10704,10 +10654,10 @@ opn@^3.0.3:
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
|
||||
optimism@^0.9.0:
|
||||
version "0.9.6"
|
||||
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.9.6.tgz#5621195486b294c3bfc518d17ac47767234b029f"
|
||||
integrity sha512-bWr/ZP32UgFCQAoSkz33XctHwpq2via2sBvGvO5JIlrU8gaiM0LvoKj3QMle9LWdSKlzKik8XGSerzsdfYLNxA==
|
||||
optimism@^0.10.0:
|
||||
version "0.10.2"
|
||||
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.10.2.tgz#626b6fd28b0923de98ecb36a3fd2d3d4e5632dd9"
|
||||
integrity sha512-zPfBIxFFWMmQboM9+Z4MSJqc1PXp82v1PFq/GfQaufI69mHKlup7ykGNnfuGIGssXJQkmhSodQ/k9EWwjd8O8A==
|
||||
dependencies:
|
||||
"@wry/context" "^0.4.0"
|
||||
|
||||
@ -13121,16 +13071,15 @@ sass-graph@^2.2.4:
|
||||
scss-tokenizer "^0.2.3"
|
||||
yargs "^7.0.0"
|
||||
|
||||
sass-loader@^7.1.0, sass-loader@~7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d"
|
||||
integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==
|
||||
sass-loader@^7.1.0, sass-loader@~7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.2.0.tgz#e34115239309d15b2527cb62b5dfefb62a96ff7f"
|
||||
integrity sha512-h8yUWaWtsbuIiOCgR9fd9c2lRXZ2uG+h8Dzg/AGNj+Hg/3TO8+BBAW9mEP+mh8ei+qBKqSJ0F1FLlYjNBc61OA==
|
||||
dependencies:
|
||||
clone-deep "^2.0.1"
|
||||
clone-deep "^4.0.1"
|
||||
loader-utils "^1.0.1"
|
||||
lodash.tail "^4.1.1"
|
||||
neo-async "^2.5.0"
|
||||
pify "^3.0.0"
|
||||
pify "^4.0.1"
|
||||
semver "^5.5.0"
|
||||
|
||||
sass-resources-loader@^2.0.0:
|
||||
@ -13313,14 +13262,12 @@ sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shallow-clone@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
|
||||
integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
|
||||
shallow-clone@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
||||
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
|
||||
dependencies:
|
||||
is-extendable "^0.1.1"
|
||||
kind-of "^5.0.0"
|
||||
mixin-object "^2.0.1"
|
||||
kind-of "^6.0.2"
|
||||
|
||||
shallow-equal@^1.1.0:
|
||||
version "1.2.0"
|
||||
@ -14459,16 +14406,11 @@ tsconfig@^7.0.0:
|
||||
strip-bom "^3.0.0"
|
||||
strip-json-comments "^2.0.0"
|
||||
|
||||
tslib@^1:
|
||||
tslib@^1, tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
|
||||
tty-browserify@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
||||
@ -14945,10 +14887,9 @@ vue-hot-reload-api@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
|
||||
integrity sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==
|
||||
|
||||
vue-izitoast@1.1.2:
|
||||
vue-izitoast@roschaefer/vue-izitoast#patch-1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-izitoast/-/vue-izitoast-1.1.2.tgz#0cf8290f045f8a389ccce4c238836c75a130eb03"
|
||||
integrity sha512-/sNVrYhFg7Moyny5tFNt2e7TTmgPB1xyy04BChKQJkN5r9/D/6vYI7KQWEtG+v9VofnIVg5Em7HXtOL8IOeT7w==
|
||||
resolved "https://codeload.github.com/roschaefer/vue-izitoast/tar.gz/c246fd78b1964c71b1889683379902d8d6284280"
|
||||
dependencies:
|
||||
izitoast "^1.3.0"
|
||||
|
||||
@ -15045,10 +14986,10 @@ vue-svg-loader@~0.12.0:
|
||||
loader-utils "^1.2.3"
|
||||
svg-to-vue "^0.4.0"
|
||||
|
||||
vue-sweetalert-icons@~4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-sweetalert-icons/-/vue-sweetalert-icons-4.0.0.tgz#49dfda05b7f8539288734d7a110d9a6ab53fd324"
|
||||
integrity sha512-C1VJpLpUSBn387VNcaBAPfsqnHdRSvJmCascLFWHrs0AXtOKEbG+XiRIHnR/K7IR3SinASPM/uBmSHFsES/PEw==
|
||||
vue-sweetalert-icons@~4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-sweetalert-icons/-/vue-sweetalert-icons-4.2.0.tgz#218d2b151ef1d364f5d147f87f03aacd92c1730f"
|
||||
integrity sha512-RNnWgdzui9mQ8bwRlJ7HkOEfAEZhTXdpIdXT8pcesFWg1y13UnqjUVvgdg8K6kqPHuVUfipMLjbewrHHjewTmg==
|
||||
dependencies:
|
||||
node-sass "^4.12.0"
|
||||
sass-loader "^7.1.0"
|
||||
@ -15607,14 +15548,6 @@ yn@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114"
|
||||
integrity sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==
|
||||
|
||||
zen-observable-ts@^0.8.18:
|
||||
version "0.8.18"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8"
|
||||
integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
zen-observable "^0.8.0"
|
||||
|
||||
zen-observable-ts@^0.8.19:
|
||||
version "0.8.19"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.19.tgz#c094cd20e83ddb02a11144a6e2a89706946b5694"
|
||||
|
||||
@ -1847,10 +1847,10 @@ cucumber@^4.2.1:
|
||||
util-arity "^1.0.2"
|
||||
verror "^1.9.0"
|
||||
|
||||
cypress-cucumber-preprocessor@^1.13.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.13.0.tgz#efacd70ce21c7d0adc60e25af166f5fb2e990fb8"
|
||||
integrity sha512-Y3B4El3oYqKUvEhfn7k7NrX/hMJvOCJIO+sgMbvvPXsUngzLWUdiS2LOAaSxpV4t2BCyFuvfzGH0j+C3tu4UvA==
|
||||
cypress-cucumber-preprocessor@^1.13.1:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.13.1.tgz#d33350343a617c7579e1fed16e169d0a23b18d7a"
|
||||
integrity sha512-gNmSVTmSVbUftvdTk0MnGGERwKTxtEQ1CwUOK4ujv5kANX29eV3XH9MYMe6gZQlVbLZN9kxz1EhopRF2bqmcwg==
|
||||
dependencies:
|
||||
"@cypress/browserify-preprocessor" "^1.1.2"
|
||||
chai "^4.1.2"
|
||||
|
||||