mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into 759-teaser-image-create-posts
This commit is contained in:
commit
7d9e6623ea
@ -21,12 +21,15 @@ install:
|
||||
- wait-on http://localhost:7474
|
||||
|
||||
script:
|
||||
- export CYPRESS_RETRIES=1
|
||||
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
|
||||
- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH"
|
||||
# Backend
|
||||
- docker-compose exec backend yarn run lint
|
||||
- docker-compose exec backend yarn run test:jest --ci --verbose=false --coverage
|
||||
- docker-compose exec backend yarn run db:reset
|
||||
- docker-compose exec backend yarn run db:seed
|
||||
- docker-compose exec backend yarn run test:cucumber
|
||||
- docker-compose exec backend yarn run test:cucumber --tags "not @wip"
|
||||
- docker-compose exec backend yarn run db:reset
|
||||
- docker-compose exec backend yarn run db:seed
|
||||
# Frontend
|
||||
@ -34,7 +37,7 @@ script:
|
||||
- docker-compose exec webapp yarn run test --ci --verbose=false --coverage
|
||||
- docker-compose exec -d backend yarn run test:before:seeder
|
||||
# Fullstack
|
||||
- CYPRESS_RETRIES=1 yarn run cypress:run
|
||||
- yarn run cypress:run
|
||||
# Coverage
|
||||
- codecov
|
||||
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -9,4 +9,4 @@
|
||||
],
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.autoFixOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,4 +24,5 @@ RUN yarn run build
|
||||
FROM base as production
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /nitro-backend/dist ./dist
|
||||
COPY ./public/img/ ./public/img/
|
||||
RUN yarn install --frozen-lockfile --non-interactive
|
||||
|
||||
@ -44,15 +44,15 @@
|
||||
"dependencies": {
|
||||
"activitystrea.ms": "~2.1.3",
|
||||
"apollo-cache-inmemory": "~1.6.2",
|
||||
"apollo-client": "~2.6.2",
|
||||
"apollo-link-context": "~1.0.14",
|
||||
"apollo-link-http": "~1.5.14",
|
||||
"apollo-server": "~2.6.2",
|
||||
"apollo-client": "~2.6.3",
|
||||
"apollo-link-context": "~1.0.18",
|
||||
"apollo-link-http": "~1.5.15",
|
||||
"apollo-server": "~2.6.3",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-alpha.31",
|
||||
"date-fns": "2.0.0-alpha.34",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.0.0",
|
||||
"express": "~4.17.1",
|
||||
@ -61,7 +61,7 @@
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.2",
|
||||
"graphql-shield": "~5.3.6",
|
||||
"graphql-shield": "~5.3.8",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.17.4",
|
||||
"helmet": "~3.18.0",
|
||||
@ -87,14 +87,14 @@
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
"@babel/preset-env": "~7.4.5",
|
||||
"@babel/register": "~7.4.4",
|
||||
"apollo-server-testing": "~2.6.2",
|
||||
"apollo-server-testing": "~2.6.3",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-eslint": "~10.0.2",
|
||||
"babel-jest": "~24.8.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~4.3.0",
|
||||
"eslint-config-prettier": "~5.0.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-plugin-import": "~2.17.3",
|
||||
"eslint-plugin-jest": "~22.6.4",
|
||||
@ -105,7 +105,7 @@
|
||||
"graphql-request": "~1.8.2",
|
||||
"jest": "~24.8.0",
|
||||
"nodemon": "~1.19.1",
|
||||
"prettier": "~1.17.1",
|
||||
"prettier": "~1.18.2",
|
||||
"supertest": "~4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,9 +505,7 @@ export default class NitroDataSource {
|
||||
const result2 = await this.client.mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
AddCommentAuthor(from: {id: "${
|
||||
result.data.CreateComment.id
|
||||
}"}, to: {id: "${toUserId}"}) {
|
||||
AddCommentAuthor(from: {id: "${result.data.CreateComment.id}"}, to: {id: "${toUserId}"}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@ -519,9 +517,7 @@ export default class NitroDataSource {
|
||||
result = await this.client.mutate({
|
||||
mutation: gql`
|
||||
mutation {
|
||||
AddCommentPost(from: { id: "${
|
||||
result.data.CreateComment.id
|
||||
}", to: { id: "${postId}" }}) {
|
||||
AddCommentPost(from: { id: "${result.data.CreateComment.id}", to: { id: "${postId}" }}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,11 @@ const legacyUrls = [
|
||||
|
||||
export const fixUrl = url => {
|
||||
legacyUrls.forEach(legacyUrl => {
|
||||
url = url.replace(legacyUrl, '/api')
|
||||
url = url.replace(legacyUrl, '')
|
||||
})
|
||||
if (!url.startsWith('/')) {
|
||||
url = `/${url}`
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { fixImageURLs } from './fixImageUrlsMiddleware'
|
||||
|
||||
describe('fixImageURLs', () => {
|
||||
describe('edge case: image url is exact match of legacy url', () => {
|
||||
it('replaces it with `/`', () => {
|
||||
const url = 'https://api-alpha.human-connection.org'
|
||||
expect(fixImageURLs(url)).toEqual('/')
|
||||
})
|
||||
})
|
||||
|
||||
describe('image url of legacy alpha', () => {
|
||||
it('removes domain', () => {
|
||||
const url =
|
||||
'https://api-alpha.human-connection.org/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png'
|
||||
expect(fixImageURLs(url)).toEqual(
|
||||
'/api/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png',
|
||||
'/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png',
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -16,7 +23,7 @@ describe('fixImageURLs', () => {
|
||||
const url =
|
||||
'https://staging-api.human-connection.org/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg'
|
||||
expect(fixImageURLs(url)).toEqual(
|
||||
'/api/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg',
|
||||
'/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -87,9 +87,7 @@ describe('currentUser { notifications }', () => {
|
||||
|
||||
describe('who mentions me again', () => {
|
||||
beforeEach(async () => {
|
||||
const updatedContent = `${
|
||||
post.content
|
||||
} One more mention to <a href="/profile/you" class="mention">@al-capone</a>`
|
||||
const updatedContent = `${post.content} One more mention to <a href="/profile/you" class="mention">@al-capone</a>`
|
||||
// The response `post.content` contains a link but the XSSmiddleware
|
||||
// should have the `mention` CSS class removed. I discovered this
|
||||
// during development and thought: A feature not a bug! This way we
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { rule, shield, allow, or } from 'graphql-shield'
|
||||
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||
|
||||
/*
|
||||
* TODO: implement
|
||||
@ -16,6 +16,12 @@ const isAdmin = rule()(async (parent, args, { user }, info) => {
|
||||
return user && user.role === 'admin'
|
||||
})
|
||||
|
||||
const onlyYourself = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (parent, args, context, info) => {
|
||||
return context.user.id === args.id
|
||||
})
|
||||
|
||||
const isMyOwn = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (parent, args, context, info) => {
|
||||
@ -48,6 +54,13 @@ const belongsToMe = rule({
|
||||
return Boolean(notification)
|
||||
})
|
||||
|
||||
/* TODO: decide if we want to remove this check: the check
|
||||
* `onlyEnabledContent` throws authorization errors only if you have
|
||||
* arguments for `disabled` or `deleted` assuming these are filter
|
||||
* parameters. Soft-delete middleware obfuscates data on its way out
|
||||
* anyways. Furthermore, `neo4j-graphql-js` offers many ways to filter for
|
||||
* data so I believe, this is not a good check anyways.
|
||||
*/
|
||||
const onlyEnabledContent = rule({
|
||||
cache: 'strict',
|
||||
})(async (parent, args, ctx, info) => {
|
||||
@ -80,47 +93,70 @@ const isAuthor = rule({
|
||||
return authorId === user.id
|
||||
})
|
||||
|
||||
// Permissions
|
||||
const permissions = shield({
|
||||
Query: {
|
||||
Notification: isAdmin,
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Post: or(onlyEnabledContent, isModerator),
|
||||
},
|
||||
Mutation: {
|
||||
UpdateNotification: belongsToMe,
|
||||
CreatePost: isAuthenticated,
|
||||
UpdatePost: isAuthor,
|
||||
DeletePost: isAuthor,
|
||||
report: isAuthenticated,
|
||||
CreateBadge: isAdmin,
|
||||
UpdateBadge: isAdmin,
|
||||
DeleteBadge: isAdmin,
|
||||
AddUserBadges: isAdmin,
|
||||
CreateSocialMedia: isAuthenticated,
|
||||
DeleteSocialMedia: isAuthenticated,
|
||||
// AddBadgeRewarded: isAdmin,
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
unreward: isAdmin,
|
||||
// addFruitToBasket: isAuthenticated
|
||||
follow: isAuthenticated,
|
||||
unfollow: isAuthenticated,
|
||||
shout: isAuthenticated,
|
||||
unshout: isAuthenticated,
|
||||
changePassword: isAuthenticated,
|
||||
enable: isModerator,
|
||||
disable: isModerator,
|
||||
CreateComment: isAuthenticated,
|
||||
DeleteComment: isAuthor,
|
||||
// CreateUser: allow,
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
password: isMyOwn,
|
||||
privateKey: isMyOwn,
|
||||
},
|
||||
const isDeletingOwnAccount = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (parent, args, context, info) => {
|
||||
return context.user.id === args.id
|
||||
})
|
||||
|
||||
// Permissions
|
||||
const permissions = shield(
|
||||
{
|
||||
Query: {
|
||||
'*': deny,
|
||||
findPosts: allow,
|
||||
Category: isAdmin,
|
||||
Tag: isAdmin,
|
||||
Report: isModerator,
|
||||
Notification: isAdmin,
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Post: or(onlyEnabledContent, isModerator),
|
||||
Comment: allow,
|
||||
User: allow,
|
||||
isLoggedIn: allow,
|
||||
},
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
login: allow,
|
||||
UpdateNotification: belongsToMe,
|
||||
CreateUser: isAdmin,
|
||||
UpdateUser: onlyYourself,
|
||||
CreatePost: isAuthenticated,
|
||||
UpdatePost: isAuthor,
|
||||
DeletePost: isAuthor,
|
||||
report: isAuthenticated,
|
||||
CreateBadge: isAdmin,
|
||||
UpdateBadge: isAdmin,
|
||||
DeleteBadge: isAdmin,
|
||||
AddUserBadges: isAdmin,
|
||||
CreateSocialMedia: isAuthenticated,
|
||||
DeleteSocialMedia: isAuthenticated,
|
||||
// AddBadgeRewarded: isAdmin,
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
unreward: isAdmin,
|
||||
// addFruitToBasket: isAuthenticated
|
||||
follow: isAuthenticated,
|
||||
unfollow: isAuthenticated,
|
||||
shout: isAuthenticated,
|
||||
unshout: isAuthenticated,
|
||||
changePassword: isAuthenticated,
|
||||
enable: isModerator,
|
||||
disable: isModerator,
|
||||
CreateComment: isAuthenticated,
|
||||
DeleteComment: isAuthor,
|
||||
DeleteUser: isDeletingOwnAccount,
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
password: isMyOwn,
|
||||
privateKey: isMyOwn,
|
||||
},
|
||||
},
|
||||
{
|
||||
fallbackRule: allow,
|
||||
},
|
||||
)
|
||||
|
||||
export default permissions
|
||||
|
||||
@ -7,12 +7,14 @@ let headers
|
||||
const factory = Factory()
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', { email: 'user@example.org', password: '1234' })
|
||||
const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' }
|
||||
await factory.create('User', adminParams)
|
||||
await factory.create('User', {
|
||||
email: 'someone@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
headers = await login({ email: 'user@example.org', password: '1234' })
|
||||
// we need to be an admin, otherwise we're not authorized to create a user
|
||||
headers = await login(adminParams)
|
||||
authenticatedClient = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
|
||||
@ -74,6 +74,22 @@ describe('CreatePost', () => {
|
||||
await expect(client.request(mutation)).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('language', () => {
|
||||
it('allows a user to set the language of the post', async () => {
|
||||
const createPostWithLanguageMutation = `
|
||||
mutation {
|
||||
CreatePost(title: "I am a title", content: "Some content", language: "en") {
|
||||
language
|
||||
}
|
||||
}
|
||||
`
|
||||
const expected = { CreatePost: { language: 'en' } }
|
||||
await expect(client.request(createPostWithLanguageMutation)).resolves.toEqual(
|
||||
expect.objectContaining(expected),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -315,6 +315,8 @@ describe('change password', () => {
|
||||
describe('do not expose private RSA key', () => {
|
||||
let headers
|
||||
let client
|
||||
let authenticatedClient
|
||||
|
||||
const queryUserPuplicKey = gql`
|
||||
query($queriedUserSlug: String) {
|
||||
User(slug: $queriedUserSlug) {
|
||||
@ -332,7 +334,7 @@ describe('do not expose private RSA key', () => {
|
||||
}
|
||||
`
|
||||
|
||||
const actionGenUserWithKeys = async () => {
|
||||
const generateUserWithKeys = async authenticatedClient => {
|
||||
// Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above.
|
||||
const variables = {
|
||||
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
|
||||
@ -341,7 +343,7 @@ describe('do not expose private RSA key', () => {
|
||||
name: 'Apfel Strudel',
|
||||
email: 'apfel-strudel@test.org',
|
||||
}
|
||||
await client.request(
|
||||
await authenticatedClient.request(
|
||||
gql`
|
||||
mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) {
|
||||
CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) {
|
||||
@ -353,14 +355,23 @@ describe('do not expose private RSA key', () => {
|
||||
)
|
||||
}
|
||||
|
||||
// not authenticate
|
||||
beforeEach(async () => {
|
||||
const adminParams = {
|
||||
role: 'admin',
|
||||
email: 'admin@example.org',
|
||||
password: '1234',
|
||||
}
|
||||
// create an admin user who has enough permissions to create other users
|
||||
await factory.create('User', adminParams)
|
||||
const headers = await login(adminParams)
|
||||
authenticatedClient = new GraphQLClient(host, { headers })
|
||||
// but also create an unauthenticated client to issue the `User` query
|
||||
client = new GraphQLClient(host)
|
||||
})
|
||||
|
||||
describe('unauthenticated query of "publicKey" (does the RSA key pair get generated at all?)', () => {
|
||||
it('returns publicKey', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await generateUserWithKeys(authenticatedClient)
|
||||
await expect(
|
||||
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||
).toEqual(
|
||||
@ -378,7 +389,7 @@ describe('do not expose private RSA key', () => {
|
||||
|
||||
describe('unauthenticated query of "privateKey"', () => {
|
||||
it('throws "Not Authorised!"', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await generateUserWithKeys(authenticatedClient)
|
||||
await expect(
|
||||
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||
).rejects.toThrow('Not Authorised')
|
||||
@ -393,7 +404,7 @@ describe('do not expose private RSA key', () => {
|
||||
|
||||
describe('authenticated query of "publicKey"', () => {
|
||||
it('returns publicKey', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await generateUserWithKeys(authenticatedClient)
|
||||
await expect(
|
||||
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||
).toEqual(
|
||||
@ -411,7 +422,7 @@ describe('do not expose private RSA key', () => {
|
||||
|
||||
describe('authenticated query of "privateKey"', () => {
|
||||
it('throws "Not Authorised!"', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await generateUserWithKeys(authenticatedClient)
|
||||
await expect(
|
||||
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
|
||||
).rejects.toThrow('Not Authorised')
|
||||
|
||||
@ -11,5 +11,27 @@ export default {
|
||||
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
},
|
||||
DeleteUser: async (object, params, context, resolveInfo) => {
|
||||
const { resource } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
if (resource && resource.length) {
|
||||
await Promise.all(
|
||||
resource.map(async node => {
|
||||
await session.run(
|
||||
`
|
||||
MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
|
||||
SET resource.deleted = true
|
||||
RETURN author`,
|
||||
{
|
||||
userId: context.user.id,
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
session.close()
|
||||
}
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host } from '../../jest/helpers'
|
||||
import { login, host } from '../../jest/helpers'
|
||||
import Factory from '../../seed/factories'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
@ -18,27 +19,58 @@ describe('users', () => {
|
||||
}
|
||||
}
|
||||
`
|
||||
client = new GraphQLClient(host)
|
||||
|
||||
it('with password and email', async () => {
|
||||
describe('given valid password and email', () => {
|
||||
const variables = {
|
||||
name: 'John Doe',
|
||||
password: '123',
|
||||
email: '123@123.de',
|
||||
}
|
||||
const expected = {
|
||||
CreateUser: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
beforeEach(async () => {
|
||||
client = new GraphQLClient(host)
|
||||
})
|
||||
|
||||
it('is not allowed to create users', async () => {
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated admin', () => {
|
||||
beforeEach(async () => {
|
||||
const adminParams = {
|
||||
role: 'admin',
|
||||
email: 'admin@example.org',
|
||||
password: '1234',
|
||||
}
|
||||
await factory.create('User', adminParams)
|
||||
const headers = await login(adminParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('is allowed to create new users', async () => {
|
||||
const expected = {
|
||||
CreateUser: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdateUser', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', { id: 'u47', name: 'John Doe' })
|
||||
})
|
||||
const userParams = {
|
||||
email: 'user@example.org',
|
||||
password: '1234',
|
||||
id: 'u47',
|
||||
name: 'John Doe',
|
||||
}
|
||||
const variables = {
|
||||
id: 'u47',
|
||||
name: 'John Doughnut',
|
||||
}
|
||||
|
||||
const mutation = `
|
||||
mutation($id: ID!, $name: String) {
|
||||
@ -48,38 +80,199 @@ describe('users', () => {
|
||||
}
|
||||
}
|
||||
`
|
||||
client = new GraphQLClient(host)
|
||||
|
||||
it('name within specifications', async () => {
|
||||
const variables = {
|
||||
id: 'u47',
|
||||
name: 'James Doe',
|
||||
}
|
||||
const expected = {
|
||||
UpdateUser: {
|
||||
id: 'u47',
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', userParams)
|
||||
})
|
||||
|
||||
describe('as another user', () => {
|
||||
beforeEach(async () => {
|
||||
const someoneElseParams = {
|
||||
email: 'someoneElse@example.org',
|
||||
password: '1234',
|
||||
name: 'James Doe',
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
}
|
||||
|
||||
await factory.create('User', someoneElseParams)
|
||||
const headers = await login(someoneElseParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('is not allowed to change other user accounts', async () => {
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
it('with no name', async () => {
|
||||
const variables = {
|
||||
id: 'u47',
|
||||
name: null,
|
||||
describe('as the same user', () => {
|
||||
beforeEach(async () => {
|
||||
const headers = await login(userParams)
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('name within specifications', async () => {
|
||||
const expected = {
|
||||
UpdateUser: {
|
||||
id: 'u47',
|
||||
name: 'John Doughnut',
|
||||
},
|
||||
}
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
it('with no name', async () => {
|
||||
const variables = {
|
||||
id: 'u47',
|
||||
name: null,
|
||||
}
|
||||
const expected = 'Username must be at least 3 characters long!'
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
|
||||
})
|
||||
|
||||
it('with too short name', async () => {
|
||||
const variables = {
|
||||
id: 'u47',
|
||||
name: ' ',
|
||||
}
|
||||
const expected = 'Username must be at least 3 characters long!'
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('DeleteUser', () => {
|
||||
let deleteUserVariables
|
||||
let asAuthor
|
||||
const deleteUserMutation = gql`
|
||||
mutation($id: ID!, $resource: [String]) {
|
||||
DeleteUser(id: $id, resource: $resource) {
|
||||
id
|
||||
contributions {
|
||||
id
|
||||
deleted
|
||||
}
|
||||
comments {
|
||||
id
|
||||
deleted
|
||||
}
|
||||
}
|
||||
}
|
||||
const expected = 'Username must be at least 3 characters long!'
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
|
||||
`
|
||||
beforeEach(async () => {
|
||||
asAuthor = await factory.create('User', {
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
id: 'u343',
|
||||
})
|
||||
await factory.create('User', {
|
||||
email: 'friendsAccount@example.org',
|
||||
password: '1234',
|
||||
id: 'u565',
|
||||
})
|
||||
deleteUserVariables = { id: 'u343', resource: [] }
|
||||
})
|
||||
|
||||
it('with too short name', async () => {
|
||||
const variables = {
|
||||
id: 'u47',
|
||||
name: ' ',
|
||||
}
|
||||
const expected = 'Username must be at least 3 characters long!'
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(deleteUserMutation, deleteUserVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
describe("attempting to delete another user's account", () => {
|
||||
it('throws an authorization error', async () => {
|
||||
deleteUserVariables = { id: 'u565' }
|
||||
await expect(client.request(deleteUserMutation, deleteUserVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('attempting to delete my own account', () => {
|
||||
let expectedResponse
|
||||
beforeEach(async () => {
|
||||
await asAuthor.authenticateAs({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
await asAuthor.create('Post', {
|
||||
id: 'p139',
|
||||
content: 'Post by user u343',
|
||||
})
|
||||
await asAuthor.create('Comment', {
|
||||
id: 'c155',
|
||||
postId: 'p139',
|
||||
content: 'Comment by user u343',
|
||||
})
|
||||
expectedResponse = {
|
||||
DeleteUser: {
|
||||
id: 'u343',
|
||||
contributions: [{ id: 'p139', deleted: false }],
|
||||
comments: [{ id: 'c155', deleted: false }],
|
||||
},
|
||||
}
|
||||
})
|
||||
it("deletes my account, but doesn't delete posts or comments by default", async () => {
|
||||
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||
expectedResponse,
|
||||
)
|
||||
})
|
||||
|
||||
describe("deletes a user's", () => {
|
||||
it('posts on request', async () => {
|
||||
deleteUserVariables = { id: 'u343', resource: ['Post'] }
|
||||
expectedResponse = {
|
||||
DeleteUser: {
|
||||
id: 'u343',
|
||||
contributions: [{ id: 'p139', deleted: true }],
|
||||
comments: [{ id: 'c155', deleted: false }],
|
||||
},
|
||||
}
|
||||
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||
expectedResponse,
|
||||
)
|
||||
})
|
||||
|
||||
it('comments on request', async () => {
|
||||
deleteUserVariables = { id: 'u343', resource: ['Comment'] }
|
||||
expectedResponse = {
|
||||
DeleteUser: {
|
||||
id: 'u343',
|
||||
contributions: [{ id: 'p139', deleted: false }],
|
||||
comments: [{ id: 'c155', deleted: true }],
|
||||
},
|
||||
}
|
||||
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||
expectedResponse,
|
||||
)
|
||||
})
|
||||
|
||||
it('posts and comments on request', async () => {
|
||||
deleteUserVariables = { id: 'u343', resource: ['Post', 'Comment'] }
|
||||
expectedResponse = {
|
||||
DeleteUser: {
|
||||
id: 'u343',
|
||||
contributions: [{ id: 'p139', deleted: true }],
|
||||
comments: [{ id: 'c155', deleted: true }],
|
||||
},
|
||||
}
|
||||
await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
|
||||
expectedResponse,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,8 +4,9 @@ type Query {
|
||||
currentUser: User
|
||||
# Get the latest Network Statistics
|
||||
statistics: Statistics!
|
||||
findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
|
||||
statement: """
|
||||
findPosts(filter: String!, limit: Int = 10): [Post]!
|
||||
@cypher(
|
||||
statement: """
|
||||
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
|
||||
YIELD node as post, score
|
||||
MATCH (post)<-[:WROTE]-(user:User)
|
||||
@ -14,8 +15,8 @@ type Query {
|
||||
AND NOT post.deleted = true AND NOT post.disabled = true
|
||||
RETURN post
|
||||
LIMIT $limit
|
||||
"""
|
||||
)
|
||||
"""
|
||||
)
|
||||
CommentByPost(postId: ID!): [Comment]!
|
||||
}
|
||||
|
||||
@ -23,7 +24,7 @@ type Mutation {
|
||||
# Get a JWT Token for the given Email and password
|
||||
login(email: String!, password: String!): String!
|
||||
signup(email: String!, password: String!): Boolean!
|
||||
changePassword(oldPassword:String!, newPassword: String!): String!
|
||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||
report(id: ID!, description: String): Report
|
||||
disable(id: ID!): ID
|
||||
enable(id: ID!): ID
|
||||
@ -37,6 +38,7 @@ type Mutation {
|
||||
follow(id: ID!, type: FollowTypeEnum): Boolean!
|
||||
# Unfollow the given Type and ID
|
||||
unfollow(id: ID!, type: FollowTypeEnum): Boolean!
|
||||
DeleteUser(id: ID!, resource: [String]): User
|
||||
}
|
||||
|
||||
type Statistics {
|
||||
@ -53,7 +55,7 @@ type Statistics {
|
||||
|
||||
type Notification {
|
||||
id: ID!
|
||||
read: Boolean,
|
||||
read: Boolean
|
||||
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
||||
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
||||
createdAt: String
|
||||
@ -80,7 +82,8 @@ type Report {
|
||||
id: ID!
|
||||
submitter: User @relation(name: "REPORTED", direction: "IN")
|
||||
description: String
|
||||
type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
|
||||
type: String!
|
||||
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
|
||||
createdAt: String
|
||||
comment: Comment @relation(name: "REPORTED", direction: "OUT")
|
||||
post: Post @relation(name: "REPORTED", direction: "OUT")
|
||||
@ -131,4 +134,3 @@ type SocialMedia {
|
||||
url: String
|
||||
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
|
||||
}
|
||||
|
||||
|
||||
@ -15,29 +15,37 @@ type Post {
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
relatedContributions: [Post]! @cypher(
|
||||
statement: """
|
||||
language: String
|
||||
relatedContributions: [Post]!
|
||||
@cypher(
|
||||
statement: """
|
||||
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||
RETURN DISTINCT post
|
||||
LIMIT 10
|
||||
"""
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
comments: [Comment]! @relation(name: "COMMENTS", direction: "IN")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
commentsCount: Int!
|
||||
@cypher(
|
||||
statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)"
|
||||
)
|
||||
|
||||
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
shoutedCount: Int!
|
||||
@cypher(
|
||||
statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
|
||||
)
|
||||
|
||||
# Has the currently logged in user shouted that post?
|
||||
shoutedByCurrentUser: Boolean! @cypher(
|
||||
statement: """
|
||||
shoutedByCurrentUser: Boolean!
|
||||
@cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@ -56,14 +56,14 @@ type User {
|
||||
contributionsCount: Int! @cypher(
|
||||
statement: """
|
||||
MATCH (this)-[:WROTE]->(r:Post)
|
||||
WHERE (NOT exists(r.deleted) OR r.deleted = false)
|
||||
AND (NOT exists(r.disabled) OR r.disabled = false)
|
||||
WHERE NOT r.deleted = true AND NOT r.disabled = true
|
||||
RETURN COUNT(r)
|
||||
"""
|
||||
)
|
||||
|
||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
commentedCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment)-[:COMMENTS]->(p:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true AND NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
|
||||
|
||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
@ -77,4 +77,4 @@ type User {
|
||||
|
||||
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
||||
badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ Feature: Follow a user
|
||||
| stuart-little |
|
||||
| tero-vota |
|
||||
|
||||
@wip
|
||||
Scenario: Send a follow to a user inbox and make sure it's added to the right followers collection
|
||||
When I send a POST request with the following activity to "/activitypub/users/tero-vota/inbox":
|
||||
"""
|
||||
|
||||
@ -27,6 +27,7 @@ Feature: Like an object like an article or note
|
||||
}
|
||||
"""
|
||||
|
||||
@wip
|
||||
Scenario: Send a like of a person to an users inbox and make sure it's added to the likes collection
|
||||
When I send a POST request with the following activity to "/activitypub/users/karl-heinz/inbox":
|
||||
"""
|
||||
|
||||
@ -1029,10 +1029,10 @@
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/express@4.16.1":
|
||||
version "4.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0"
|
||||
integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==
|
||||
"@types/express@4.17.0":
|
||||
version "4.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287"
|
||||
integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
@ -1119,10 +1119,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
|
||||
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
|
||||
|
||||
"@types/yup@0.26.14":
|
||||
version "0.26.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.14.tgz#d31f3b9a04039cca70ebb4db4d6c7fc3f694e80b"
|
||||
integrity sha512-OcBtVLHvYULVSltpuBdhFiVOKoSsOS58D872HydO93oBf3OdGq5zb+LnqGo18TNNSV2aW8hjIdS6H+wp68zFtQ==
|
||||
"@types/yup@0.26.16":
|
||||
version "0.26.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.16.tgz#75c428236207c48d9f8062dd1495cda8c5485a15"
|
||||
integrity sha512-E2RNc7DSeQ+2EIJ1H3+yFjYu6YiyQBUJ7yNpIxomrYJ3oFizLZ5yDS3T1JTUNBC2OCRkgnhLS0smob5UuCHfNA==
|
||||
|
||||
"@types/zen-observable@^0.5.3":
|
||||
version "0.5.4"
|
||||
@ -1322,10 +1322,10 @@ apollo-cache@1.3.2, apollo-cache@^1.3.2:
|
||||
apollo-utilities "^1.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-client@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.2.tgz#03b6af651e09b6e413e486ddc87464c85bd6e514"
|
||||
integrity sha512-oks1MaT5x7gHcPeC8vPC1UzzsKaEIC0tye+jg72eMDt5OKc7BobStTeS/o2Ib3e0ii40nKxGBnMdl/Xa/p56Yg==
|
||||
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==
|
||||
dependencies:
|
||||
"@types/zen-observable" "^0.8.0"
|
||||
apollo-cache "1.3.2"
|
||||
@ -1351,14 +1351,14 @@ apollo-engine-reporting-protobuf@0.3.1:
|
||||
dependencies:
|
||||
protobufjs "^6.8.6"
|
||||
|
||||
apollo-engine-reporting@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.0.tgz#50151811a0f5e70f4a73e7092a61fec422d8e722"
|
||||
integrity sha512-xP+Z+wdQH4ee7xfuP3WsJcIe30AH68gpp2lQm2+rnW5JfjIqD5YehSoO2Svi2jK3CSv8Y561i3QMW9i34P7hEQ==
|
||||
apollo-engine-reporting@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.1.tgz#f2c2c63f865871a57c15cdbb2a3bcd4b4af28115"
|
||||
integrity sha512-e0Xp+0yite8DH/xm9fnJt42CxfWAcY6waiq3icCMAgO9T7saXzVOPpl84SkuA+hIJUBtfaKrTnC+7Jxi/I7OrQ==
|
||||
dependencies:
|
||||
apollo-engine-reporting-protobuf "0.3.1"
|
||||
apollo-graphql "^0.3.0"
|
||||
apollo-server-core "2.6.2"
|
||||
apollo-server-core "2.6.3"
|
||||
apollo-server-env "2.4.0"
|
||||
async-retry "^1.2.1"
|
||||
graphql-extensions "0.7.2"
|
||||
@ -1388,41 +1388,41 @@ apollo-graphql@^0.3.0:
|
||||
apollo-env "0.5.1"
|
||||
lodash.sortby "^4.7.0"
|
||||
|
||||
apollo-link-context@~1.0.14:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.17.tgz#439272cfb43ec1891506dd175ed907845b7de36c"
|
||||
integrity sha512-W5UUfHcrrlP5uqJs5X1zbf84AMXhPZGAqX/7AQDgR6wY/7//sMGfJvm36KDkpIeSOElztGtM9z6zdPN1NbT41Q==
|
||||
apollo-link-context@~1.0.18:
|
||||
version "1.0.18"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.18.tgz#9e700e3314da8ded50057fee0a18af2bfcedbfc3"
|
||||
integrity sha512-aG5cbUp1zqOHHQjAJXG7n/izeMQ6LApd/whEF5z6qZp5ATvcyfSNkCfy3KRJMMZZ3iNfVTs6jF+IUA8Zvf+zeg==
|
||||
dependencies:
|
||||
apollo-link "^1.2.11"
|
||||
apollo-link "^1.2.12"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link-http-common@^0.2.13:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
|
||||
integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
|
||||
apollo-link-http-common@^0.2.14:
|
||||
version "0.2.14"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.14.tgz#d3a195c12e00f4e311c417f121181dcc31f7d0c8"
|
||||
integrity sha512-v6mRU1oN6XuX8beVIRB6OpF4q1ULhSnmy7ScnHnuo1qV6GaFmDcbdvXqxIkAV1Q8SQCo2lsv4HeqJOWhFfApOg==
|
||||
dependencies:
|
||||
apollo-link "^1.2.11"
|
||||
ts-invariant "^0.3.2"
|
||||
apollo-link "^1.2.12"
|
||||
ts-invariant "^0.4.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link-http@~1.5.14:
|
||||
version "1.5.14"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.14.tgz#ed6292248d1819ccd16523e346d35203a1b31109"
|
||||
integrity sha512-XEoPXmGpxFG3wioovgAlPXIarWaW4oWzt8YzjTYZ87R4R7d1A3wKR/KcvkdMV1m5G7YSAHcNkDLe/8hF2nH6cg==
|
||||
apollo-link-http@~1.5.15:
|
||||
version "1.5.15"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.15.tgz#106ab23bb8997bd55965d05855736d33119652cf"
|
||||
integrity sha512-epZFhCKDjD7+oNTVK3P39pqWGn4LEhShAoA1Q9e2tDrBjItNfviiE33RmcLcCURDYyW5JA6SMgdODNI4Is8tvQ==
|
||||
dependencies:
|
||||
apollo-link "^1.2.11"
|
||||
apollo-link-http-common "^0.2.13"
|
||||
apollo-link "^1.2.12"
|
||||
apollo-link-http-common "^0.2.14"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link@^1.0.0, 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==
|
||||
apollo-link@^1.0.0, 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==
|
||||
dependencies:
|
||||
apollo-utilities "^1.2.1"
|
||||
ts-invariant "^0.3.2"
|
||||
apollo-utilities "^1.3.0"
|
||||
ts-invariant "^0.4.0"
|
||||
tslib "^1.9.3"
|
||||
zen-observable-ts "^0.8.18"
|
||||
zen-observable-ts "^0.8.19"
|
||||
|
||||
apollo-server-caching@0.4.0:
|
||||
version "0.4.0"
|
||||
@ -1431,17 +1431,17 @@ apollo-server-caching@0.4.0:
|
||||
dependencies:
|
||||
lru-cache "^5.0.0"
|
||||
|
||||
apollo-server-core@2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.2.tgz#a792b50d4df9e26ec03759a31fbcbce38361b218"
|
||||
integrity sha512-AbAnfoQ26NPsNIyBa/BVKBtA/wRsNL/E6eEem1VIhzitfgO25bVXFbEZDLxbgz6wvJ+veyRFpse7Qi1bvRpxOw==
|
||||
apollo-server-core@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.3.tgz#786c8251c82cf29acb5cae9635a321f0644332ae"
|
||||
integrity sha512-tfC0QO1NbJW3ShkB5pRCnUaYEkW2AwnswaTeedkfv//EO3yiC/9LeouCK5F22T8stQG+vGjvCqf0C8ldI/XsIA==
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.3.6"
|
||||
"@apollographql/graphql-playground-html" "1.6.20"
|
||||
"@types/ws" "^6.0.0"
|
||||
apollo-cache-control "0.7.2"
|
||||
apollo-datasource "0.5.0"
|
||||
apollo-engine-reporting "1.3.0"
|
||||
apollo-engine-reporting "1.3.1"
|
||||
apollo-server-caching "0.4.0"
|
||||
apollo-server-env "2.4.0"
|
||||
apollo-server-errors "2.3.0"
|
||||
@ -1479,18 +1479,18 @@ apollo-server-errors@2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
|
||||
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
|
||||
|
||||
apollo-server-express@2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.2.tgz#526297c01a7a32fe9215566f9fd7ff92e82f1fa0"
|
||||
integrity sha512-nbL3noJ5KxKGg+hT8UsAA7++oHWq/KNSevfdCluWTfUNqH1vYRTvAnARx/6JM06S9zcPTfOLcqwHnDnY9zYFxA==
|
||||
apollo-server-express@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.3.tgz#62034c978f84207615c0430fb37ab006f71146fe"
|
||||
integrity sha512-8ca+VpKArgNzFar0D3DesWnn0g9YDtFLhO56TQprHh2Spxu9WxTnYNjsYs2MCCNf+iV/uy7vTvEknErvnIcZaQ==
|
||||
dependencies:
|
||||
"@apollographql/graphql-playground-html" "1.6.20"
|
||||
"@types/accepts" "^1.3.5"
|
||||
"@types/body-parser" "1.17.0"
|
||||
"@types/cors" "^2.8.4"
|
||||
"@types/express" "4.16.1"
|
||||
"@types/express" "4.17.0"
|
||||
accepts "^1.3.5"
|
||||
apollo-server-core "2.6.2"
|
||||
apollo-server-core "2.6.3"
|
||||
body-parser "^1.18.3"
|
||||
cors "^2.8.4"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
@ -1523,20 +1523,20 @@ apollo-server-plugin-base@0.5.2:
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.2.tgz#f97ba983f1e825fec49cba8ff6a23d00e1901819"
|
||||
integrity sha512-j81CpadRLhxikBYHMh91X4aTxfzFnmmebEiIR9rruS6dywWCxV2aLW87l9ocD1MiueNam0ysdwZkX4F3D4csNw==
|
||||
|
||||
apollo-server-testing@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.2.tgz#e0ecddd565fce1c38a346f9fbe6118f543ccf6a6"
|
||||
integrity sha512-I9QLFk4I/z9oOIXfnLc8RPBYAKih6Olrg3RDeRvWhDjLQ8gfALXVhCO+7WuvM35wNZcZVn7aXBeZ8Y3mlgkj8w==
|
||||
apollo-server-testing@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.3.tgz#a0199a5d42000e60ecf0dea44b851f5f581e280e"
|
||||
integrity sha512-LTkegcGVSkM+pA0FINDSYVl3TiFYKZyfjlKrEr/LN6wLiL6gbRgy6LMtk2j+qli/bnTDqqQREX8OEqmV8FKUoQ==
|
||||
dependencies:
|
||||
apollo-server-core "2.6.2"
|
||||
apollo-server-core "2.6.3"
|
||||
|
||||
apollo-server@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.2.tgz#33fe894b740588f059a7679346516ffce50377d5"
|
||||
integrity sha512-fMXaAKIb0dX0lzcZ4zlu7ay1L596d9HTNkdn8cKuM7zmTpugZSAL966COguJUDSjUS9CaB1Kh5hl1yRuRqHXSA==
|
||||
apollo-server@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.3.tgz#71235325449c6d3881a5143975ca44c07a07d2d7"
|
||||
integrity sha512-pTIXE5xEMAikKLTIBIqLNvimMETiZbzmiqDb6BGzIUicAz4Rxa1/+bDi1ZeJWrZQjE/TfBLd2Si3qam7dZGrjw==
|
||||
dependencies:
|
||||
apollo-server-core "2.6.2"
|
||||
apollo-server-express "2.6.2"
|
||||
apollo-server-core "2.6.3"
|
||||
apollo-server-express "2.6.3"
|
||||
express "^4.0.0"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
graphql-tools "^4.0.0"
|
||||
@ -1566,7 +1566,7 @@ apollo-upload-server@^7.0.0:
|
||||
http-errors "^1.7.0"
|
||||
object-path "^0.11.4"
|
||||
|
||||
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.2.1, apollo-utilities@^1.3.2:
|
||||
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
|
||||
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
|
||||
@ -1790,10 +1790,10 @@ babel-core@~7.0.0-0:
|
||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
||||
|
||||
babel-eslint@~10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
|
||||
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
|
||||
babel-eslint@~10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||
integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/parser" "^7.0.0"
|
||||
@ -2586,10 +2586,10 @@ data-urls@^1.0.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@2.0.0-alpha.31:
|
||||
version "2.0.0-alpha.31"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.31.tgz#51bcfdca25dfc9bea334a556ab33dfc0bb00421c"
|
||||
integrity sha512-S19PwMqnbYsqcbCg02Yj9gv4veVNZ0OX7v2+zcd+Mq0RI7LoDKJipJjnMrTZ3Cc6blDuTce5G/pHXcVIGRwJWQ==
|
||||
date-fns@2.0.0-alpha.34:
|
||||
version "2.0.0-alpha.34"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.34.tgz#5d3ae7ca0d08915ccfc87a20545250af4e9c3cae"
|
||||
integrity sha512-yjSYUHASHvzOZl++cEms+Tw7oQOFA+7Z6/lL7L3lRO9j6pMfT48N6oEyvCGo/MVlH08XWmydgf8X9Y1eedf9sQ==
|
||||
|
||||
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
@ -2995,10 +2995,10 @@ escodegen@^1.9.1:
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-config-prettier@~4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0"
|
||||
integrity sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==
|
||||
eslint-config-prettier@~5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c"
|
||||
integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
@ -3772,12 +3772,12 @@ graphql-request@~1.8.2:
|
||||
dependencies:
|
||||
cross-fetch "2.2.2"
|
||||
|
||||
graphql-shield@~5.3.6:
|
||||
version "5.3.6"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.6.tgz#20061b02f77056c0870a623c530ef28a1bf4fff4"
|
||||
integrity sha512-ihw/i4X+d1kpj1SVA6iBkVl2DZhPsI+xV08geR2TX3FWhpU7zakk/16yBzDRJTTCUgKsWfgyebrgIBsuhTwMnA==
|
||||
graphql-shield@~5.3.8:
|
||||
version "5.3.8"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.8.tgz#f9e7ad2285f6cfbe20a8a49154ce6c1b184e3893"
|
||||
integrity sha512-33rQ8U5jMurHIapctHk7hBcUg3nxC7fmMIMtyWiomJXhBmztFq/SG7jNaapnL5M7Q/0BmoaSQd3FLSpelP9KPw==
|
||||
dependencies:
|
||||
"@types/yup" "0.26.14"
|
||||
"@types/yup" "0.26.16"
|
||||
lightercollective "^0.3.0"
|
||||
object-hash "^1.3.1"
|
||||
yup "^0.27.0"
|
||||
@ -6230,10 +6230,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@~1.17.1:
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.1.tgz#ed64b4e93e370cb8a25b9ef7fef3e4fd1c0995db"
|
||||
integrity sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==
|
||||
prettier@~1.18.2:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||
|
||||
pretty-format@^24.8.0:
|
||||
version "24.8.0"
|
||||
@ -7543,13 +7543,6 @@ trunc-text@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5"
|
||||
integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU=
|
||||
|
||||
ts-invariant@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
|
||||
integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
|
||||
ts-invariant@^0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.2.tgz#8685131b8083e67c66d602540e78763408be9113"
|
||||
@ -8112,10 +8105,10 @@ yup@^0.27.0:
|
||||
synchronous-promise "^2.0.6"
|
||||
toposort "^2.0.2"
|
||||
|
||||
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==
|
||||
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"
|
||||
integrity sha512-u1a2rpE13G+jSzrg3aiCqXU5tN2kw41b+cBZGmnc+30YimdkKiDj9bTowcB41eL77/17RF/h+393AuVgShyheQ==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
zen-observable "^0.8.0"
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
# SSH Access
|
||||
# SSH_USERNAME='username'
|
||||
# SSH_HOST='example.org'
|
||||
|
||||
# UPLOADS_DIRECTORY=/var/www/api/uploads
|
||||
OUTPUT_DIRECTORY='/uploads/'
|
||||
@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# import .env config
|
||||
set -o allexport
|
||||
source $(dirname "$0")/.env
|
||||
set +o allexport
|
||||
|
||||
for var in "SSH_USERNAME" "SSH_HOST" "UPLOADS_DIRECTORY"
|
||||
do
|
||||
if [[ -z "${!var}" ]]; then
|
||||
@ -9,4 +14,4 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ /uploads/
|
||||
rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ ${OUTPUT_DIRECTORY}
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
"codecov": "^3.5.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"cypress": "^3.3.1",
|
||||
"cypress-cucumber-preprocessor": "^1.11.2",
|
||||
"cypress-file-upload": "^3.1.2",
|
||||
"cypress-cucumber-preprocessor": "^1.12.0",
|
||||
"cypress-file-upload": "^3.1.4",
|
||||
"cypress-plugin-retries": "^1.2.2",
|
||||
"dotenv": "^8.0.0",
|
||||
"faker": "^4.1.0",
|
||||
|
||||
@ -10,7 +10,7 @@ $easeOut: cubic-bezier(0.19, 1, 0.22, 1);
|
||||
&::before {
|
||||
@include border-radius($border-radius-x-large);
|
||||
box-shadow: inset 0 0 0 5px $color-danger;
|
||||
content: "";
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@ -102,10 +102,10 @@ hr {
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
[class$=menu-trigger] {
|
||||
[class$='menu-trigger'] {
|
||||
user-select: none;
|
||||
}
|
||||
[class$=menu-popover] {
|
||||
[class$='menu-popover'] {
|
||||
display: inline-block;
|
||||
|
||||
nav {
|
||||
@ -145,10 +145,11 @@ hr {
|
||||
}
|
||||
}
|
||||
|
||||
[class$="menu-popover"] {
|
||||
[class$='menu-popover'] {
|
||||
min-width: 130px;
|
||||
|
||||
a, button {
|
||||
a,
|
||||
button {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Avatar from './Avatar.vue'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
|
||||
describe('Avatar.vue', () => {
|
||||
let propsData = {}
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<ds-avatar :image="avatarUrl" :name="userName" class="avatar" :size="size" />
|
||||
<ds-avatar
|
||||
:image="user && user.avatar | proxyApiUrl"
|
||||
:name="userName"
|
||||
class="avatar"
|
||||
:size="size"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -10,11 +15,6 @@ export default {
|
||||
size: { type: String, default: 'small' },
|
||||
},
|
||||
computed: {
|
||||
avatarUrl() {
|
||||
const { avatar: imageSrc } = this.user || {}
|
||||
if (!imageSrc) return imageSrc
|
||||
return imageSrc.startsWith('/') ? imageSrc.replace('/', '/api/') : imageSrc
|
||||
},
|
||||
userName() {
|
||||
const { name } = this.user || {}
|
||||
// The name is used to display the initials in case
|
||||
|
||||
@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<div :class="[badges.length === 2 && 'hc-badges-dual']" class="hc-badges">
|
||||
<div v-for="badge in badges" :key="badge.key" class="hc-badge-container">
|
||||
<hc-image :title="badge.key" :image-props="{ src: badge.icon }" class="hc-badge" />
|
||||
<img :title="badge.key" :src="badge.icon | proxyApiUrl" class="hc-badge" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcImage from './Image'
|
||||
export default {
|
||||
components: {
|
||||
HcImage,
|
||||
},
|
||||
props: {
|
||||
badges: {
|
||||
type: Array,
|
||||
|
||||
@ -25,6 +25,9 @@ describe('Comment.vue', () => {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
$filters: {
|
||||
truncate: a => a,
|
||||
},
|
||||
$apollo: {
|
||||
mutate: jest.fn().mockResolvedValue(),
|
||||
},
|
||||
|
||||
@ -9,12 +9,13 @@
|
||||
<ds-space margin-bottom="x-small">
|
||||
<hc-user :user="author" :date-time="comment.createdAt" />
|
||||
</ds-space>
|
||||
<!-- Content Menu (can open Modals) -->
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
resource-type="comment"
|
||||
:resource="comment"
|
||||
:callbacks="{ confirm: deleteCommentCallback, cancel: null }"
|
||||
:modalsData="menuModalsData"
|
||||
style="float-right"
|
||||
:is-owner="isAuthor(author.id)"
|
||||
/>
|
||||
@ -59,6 +60,30 @@ export default {
|
||||
if (this.deleted) return {}
|
||||
return this.comment.author || {}
|
||||
},
|
||||
menuModalsData() {
|
||||
return {
|
||||
delete: {
|
||||
titleIdent: 'delete.comment.title',
|
||||
messageIdent: 'delete.comment.message',
|
||||
messageParams: {
|
||||
name: this.$filters.truncate(this.comment.contentExcerpt, 30),
|
||||
},
|
||||
buttons: {
|
||||
confirm: {
|
||||
danger: true,
|
||||
icon: 'trash',
|
||||
textIdent: 'delete.submit',
|
||||
callback: this.deleteCommentCallback,
|
||||
},
|
||||
cancel: {
|
||||
icon: 'close',
|
||||
textIdent: 'delete.cancel',
|
||||
callback: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isAuthor(id) {
|
||||
|
||||
@ -43,7 +43,13 @@ export default {
|
||||
return value.match(/(contribution|comment|organization|user)/)
|
||||
},
|
||||
},
|
||||
callbacks: { type: Object, required: true },
|
||||
modalsData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
// default: () => {
|
||||
// return {}
|
||||
// },
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
@ -145,7 +151,7 @@ export default {
|
||||
data: {
|
||||
type: this.resourceType,
|
||||
resource: this.resource,
|
||||
callbacks: this.callbacks,
|
||||
modalsData: this.modalsData,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
152
webapp/components/ContributionForm/ContributionForm.spec.js
Normal file
152
webapp/components/ContributionForm/ContributionForm.spec.js
Normal file
@ -0,0 +1,152 @@
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import ContributionForm from './index.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||
|
||||
describe('ContributionForm.vue', () => {
|
||||
let wrapper
|
||||
let postTitleInput
|
||||
let expectedParams
|
||||
let deutschOption
|
||||
let cancelBtn
|
||||
let mocks
|
||||
const postTitle = 'this is a title for a post'
|
||||
const postContent = 'this is a post'
|
||||
const computed = { locale: () => 'English' }
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
CreatePost: {
|
||||
title: postTitle,
|
||||
slug: 'this-is-a-title-for-a-post',
|
||||
content: postContent,
|
||||
contentExcerpt: postContent,
|
||||
language: 'en',
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockRejectedValue({ message: 'Not Authorised!' }),
|
||||
},
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn(),
|
||||
},
|
||||
$i18n: {
|
||||
locale: () => 'en',
|
||||
},
|
||||
$router: {
|
||||
back: jest.fn(),
|
||||
push: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const getters = {
|
||||
'editor/placeholder': () => {
|
||||
return 'some cool placeholder'
|
||||
},
|
||||
}
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionForm, { mocks, localVue, computed, store })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.setData({ form: { languageOptions: [{ label: 'Deutsch', value: 'de' }] } })
|
||||
})
|
||||
|
||||
describe('CreatePost', () => {
|
||||
describe('invalid form submission', () => {
|
||||
it('title required for form submission', async () => {
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
postTitleInput.setValue('this is a title for a post')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('content required for form submission', async () => {
|
||||
wrapper.vm.updateEditorContent('this is a post')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid form submission', () => {
|
||||
expectedParams = {
|
||||
variables: { title: postTitle, content: postContent, language: 'en', id: null },
|
||||
}
|
||||
beforeEach(async () => {
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
postTitleInput.setValue('this is a title for a post')
|
||||
wrapper.vm.updateEditorContent('this is a post')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('with title and content', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("sends a fallback language based on a user's locale", () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('supports changing the language', async () => {
|
||||
expectedParams.variables.language = 'de'
|
||||
deutschOption = wrapper.findAll('li').at(0)
|
||||
deutschOption.trigger('click')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it("pushes the user to the post's page", async () => {
|
||||
expect(mocks.$router.push).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('shows a success toaster', () => {
|
||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cancel', () => {
|
||||
it('calls $router.back() when cancel button clicked', () => {
|
||||
cancelBtn = wrapper.find('.cancel-button')
|
||||
cancelBtn.trigger('click')
|
||||
expect(mocks.$router.back).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('handles errors', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper = Wrapper()
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
postTitleInput.setValue('this is a title for a post')
|
||||
wrapper.vm.updateEditorContent('this is a post')
|
||||
// second submission causes mutation to reject
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
it('shows an error toaster when apollo mutation rejects', async () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await mocks.$apollo.mutate
|
||||
expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -21,8 +21,27 @@
|
||||
<no-ssr>
|
||||
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
|
||||
</no-ssr>
|
||||
<ds-space margin-bottom="xxx-large" />
|
||||
<ds-flex class="contribution-form-footer">
|
||||
<ds-flex-item :width="{ base: '10%', sm: '10%', md: '10%', lg: '15%' }" />
|
||||
<ds-flex-item :width="{ base: '80%', sm: '30%', md: '30%', lg: '20%' }">
|
||||
<ds-space margin-bottom="small" />
|
||||
<ds-select
|
||||
model="language"
|
||||
:options="form.languageOptions"
|
||||
icon="globe"
|
||||
:placeholder="locale"
|
||||
:label="$t('contribution.languageSelectLabel')"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<div slot="footer" style="text-align: right">
|
||||
<ds-button :disabled="loading || disabled" ghost @click.prevent="$router.back()">
|
||||
<ds-button
|
||||
:disabled="loading || disabled"
|
||||
ghost
|
||||
class="cancel-button"
|
||||
@click="$router.back()"
|
||||
>
|
||||
{{ $t('actions.cancel') }}
|
||||
</ds-button>
|
||||
<ds-button
|
||||
@ -45,6 +64,9 @@
|
||||
import gql from 'graphql-tag'
|
||||
import vueDropzone from 'nuxt-dropzone'
|
||||
import HcEditor from '~/components/Editor'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import locales from '~/locales'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -60,6 +82,8 @@ export default {
|
||||
title: '',
|
||||
content: '',
|
||||
teaserImage: '',
|
||||
language: null,
|
||||
languageOptions: [],
|
||||
},
|
||||
formSchema: {
|
||||
title: { required: true, min: 3, max: 64 },
|
||||
@ -89,26 +113,38 @@ export default {
|
||||
this.slug = contribution.slug
|
||||
this.form.content = contribution.content
|
||||
this.form.title = contribution.title
|
||||
this.form.language = this.locale
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
locale() {
|
||||
const locale =
|
||||
this.contribution && this.contribution.language
|
||||
? locales.find(loc => this.contribution.language === loc.code)
|
||||
: locales.find(loc => this.$i18n.locale() === loc.code)
|
||||
return locale.name
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.availableLocales()
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
const postMutations = require('~/graphql/PostMutations.js').default(this)
|
||||
this.loading = true
|
||||
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: this.id ? postMutations.UpdatePost : postMutations.CreatePost,
|
||||
mutation: this.id ? PostMutations().UpdatePost : PostMutations().CreatePost,
|
||||
variables: {
|
||||
id: this.id,
|
||||
title: this.form.title,
|
||||
content: this.form.content,
|
||||
language: this.form.language ? this.form.language.value : this.$i18n.locale(),
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
this.loading = false
|
||||
this.$toast.success('Saved!')
|
||||
this.$toast.success(this.$t('contribution.success'))
|
||||
this.disabled = true
|
||||
|
||||
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost']
|
||||
@ -140,6 +176,11 @@ export default {
|
||||
this.form.teaserImage = file[0]
|
||||
},
|
||||
verror(file, message) {},
|
||||
availableLocales() {
|
||||
orderBy(locales, 'name').map(locale => {
|
||||
this.form.languageOptions.push({ label: locale.name, value: locale.code })
|
||||
})
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
User: {
|
||||
@ -228,5 +269,7 @@ export default {
|
||||
.hc-attachments-upload-area:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
.contribution-form-footer {
|
||||
border-top: $border-size-base solid $border-color-softest;
|
||||
}
|
||||
</style>
|
||||
|
||||
183
webapp/components/DeleteData/DeleteData.spec.js
Normal file
183
webapp/components/DeleteData/DeleteData.spec.js
Normal file
@ -0,0 +1,183 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import DeleteData from './DeleteData.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('DeleteData.vue', () => {
|
||||
let mocks
|
||||
let wrapper
|
||||
let getters
|
||||
let actions
|
||||
let deleteAccountBtn
|
||||
let enableDeletionInput
|
||||
let enableContributionDeletionCheckbox
|
||||
let enableCommentDeletionCheckbox
|
||||
const deleteAccountName = 'Delete MyAccount'
|
||||
const deleteContributionsMessage = 'Delete my 2 posts'
|
||||
const deleteCommentsMessage = 'Delete my 3 comments'
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: {
|
||||
DeleteData: {
|
||||
id: 'u343',
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockRejectedValue({ message: 'Not authorised!' }),
|
||||
},
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn(),
|
||||
},
|
||||
$router: {
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
},
|
||||
},
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return { id: 'u343', name: deleteAccountName, contributionsCount: 2, commentsCount: 3 }
|
||||
},
|
||||
}
|
||||
actions = { 'auth/logout': jest.fn() }
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
actions,
|
||||
})
|
||||
return mount(DeleteData, { mocks, localVue, store })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('defaults to deleteContributions to false', () => {
|
||||
expect(wrapper.vm.deleteContributions).toEqual(false)
|
||||
})
|
||||
|
||||
it('defaults to deleteComments to false', () => {
|
||||
expect(wrapper.vm.deleteComments).toEqual(false)
|
||||
})
|
||||
|
||||
it('defaults to deleteEnabled to false', () => {
|
||||
expect(wrapper.vm.deleteEnabled).toEqual(false)
|
||||
})
|
||||
|
||||
it('does not call the delete user mutation if deleteEnabled is false', () => {
|
||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('calls the delete user mutation', () => {
|
||||
beforeEach(() => {
|
||||
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||
enableDeletionInput.setValue(deleteAccountName)
|
||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||
})
|
||||
|
||||
it('if deleteEnabled is true and only deletes user by default', () => {
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
id: 'u343',
|
||||
resource: [],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("deletes a user's posts if requested", () => {
|
||||
mocks.$t.mockImplementation(() => deleteContributionsMessage)
|
||||
enableContributionDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(0)
|
||||
enableContributionDeletionCheckbox.trigger('click')
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
id: 'u343',
|
||||
resource: ['Post'],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("deletes a user's comments if requested", () => {
|
||||
mocks.$t.mockImplementation(() => deleteCommentsMessage)
|
||||
enableCommentDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(1)
|
||||
enableCommentDeletionCheckbox.trigger('click')
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
id: 'u343',
|
||||
resource: ['Comment'],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("deletes a user's posts and comments if requested", () => {
|
||||
mocks.$t.mockImplementation(() => deleteContributionsMessage)
|
||||
enableContributionDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(0)
|
||||
enableContributionDeletionCheckbox.trigger('click')
|
||||
mocks.$t.mockImplementation(() => deleteCommentsMessage)
|
||||
enableCommentDeletionCheckbox = wrapper.findAll('.checkbox-container input').at(1)
|
||||
enableCommentDeletionCheckbox.trigger('click')
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
id: 'u343',
|
||||
resource: ['Post', 'Comment'],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('shows a success toaster after successful mutation', async () => {
|
||||
await deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('redirect the user to the homepage', async () => {
|
||||
await deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$router.history.push).toHaveBeenCalledWith('/')
|
||||
})
|
||||
})
|
||||
|
||||
describe('error handling', () => {
|
||||
it('shows an error toaster when the mutation rejects', async () => {
|
||||
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||
enableDeletionInput.setValue(deleteAccountName)
|
||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||
await deleteAccountBtn.trigger('click')
|
||||
// second submission causes mutation to reject
|
||||
await deleteAccountBtn.trigger('click')
|
||||
await mocks.$apollo.mutate
|
||||
expect(mocks.$toast.error).toHaveBeenCalledWith('Not authorised!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
224
webapp/components/DeleteData/DeleteData.vue
Normal file
224
webapp/components/DeleteData/DeleteData.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-card hover>
|
||||
<ds-space />
|
||||
<ds-container>
|
||||
<ds-flex>
|
||||
<ds-flex-item :width="{ base: '22%', sm: '12%', md: '12%', lg: '8%' }">
|
||||
<ds-icon name="warning" size="xxx-large" class="delete-warning-icon" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '78%', sm: '88%', md: '88%', lg: '92%' }">
|
||||
<ds-heading>{{ $t('settings.deleteUserAccount.name') }}</ds-heading>
|
||||
</ds-flex-item>
|
||||
<ds-space />
|
||||
<ds-heading tag="h4">
|
||||
{{ $t('settings.deleteUserAccount.accountDescription') }}
|
||||
</ds-heading>
|
||||
</ds-flex>
|
||||
</ds-container>
|
||||
<ds-space />
|
||||
<ds-container>
|
||||
<transition name="slide-up">
|
||||
<div v-if="deleteEnabled">
|
||||
<label v-if="currentUser.contributionsCount" class="checkbox-container">
|
||||
<input type="checkbox" v-model="deleteContributions" />
|
||||
<span class="checkmark"></span>
|
||||
{{
|
||||
$t('settings.deleteUserAccount.contributionsCount', {
|
||||
count: currentUser.contributionsCount,
|
||||
})
|
||||
}}
|
||||
</label>
|
||||
<ds-space margin-bottom="small" />
|
||||
<label v-if="currentUser.commentsCount" class="checkbox-container">
|
||||
<input type="checkbox" v-model="deleteComments" />
|
||||
<span class="checkmark"></span>
|
||||
{{
|
||||
$t('settings.deleteUserAccount.commentsCount', {
|
||||
count: currentUser.commentsCount,
|
||||
})
|
||||
}}
|
||||
</label>
|
||||
<ds-space margin-bottom="small" />
|
||||
<ds-section id="delete-user-account-warning">
|
||||
<div v-html="$t('settings.deleteUserAccount.accountWarning')"></div>
|
||||
</ds-section>
|
||||
</div>
|
||||
</transition>
|
||||
</ds-container>
|
||||
<template slot="footer" class="delete-data-footer">
|
||||
<ds-container>
|
||||
<div
|
||||
class="delete-input-label"
|
||||
v-html="$t('settings.deleteUserAccount.pleaseConfirm', { confirm: currentUser.name })"
|
||||
></div>
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'large' }">
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1.75 }">
|
||||
<ds-input
|
||||
v-model="enableDeletionValue"
|
||||
@input="enableDeletion"
|
||||
class="enable-deletion-input"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1 }">
|
||||
<ds-button icon="trash" danger :disabled="!deleteEnabled" @click="handleSubmit">
|
||||
{{ $t('settings.deleteUserAccount.name') }}
|
||||
</ds-button>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-container>
|
||||
</template>
|
||||
</ds-card>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
name: 'DeleteData',
|
||||
data() {
|
||||
return {
|
||||
deleteContributions: false,
|
||||
deleteComments: false,
|
||||
deleteEnabled: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
logout: 'auth/logout',
|
||||
}),
|
||||
enableDeletion() {
|
||||
if (this.enableDeletionValue === this.currentUser.name) {
|
||||
this.deleteEnabled = true
|
||||
}
|
||||
},
|
||||
handleSubmit() {
|
||||
let resourceArgs = []
|
||||
if (this.deleteContributions) {
|
||||
resourceArgs.push('Post')
|
||||
}
|
||||
if (this.deleteComments) {
|
||||
resourceArgs.push('Comment')
|
||||
}
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!, $resource: [String]) {
|
||||
DeleteUser(id: $id, resource: $resource) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: this.currentUser.id, resource: resourceArgs },
|
||||
})
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t('settings.deleteUserAccount.success'))
|
||||
this.logout()
|
||||
this.$router.history.push('/')
|
||||
})
|
||||
.catch(error => {
|
||||
this.$toast.error(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.delete-warning-icon {
|
||||
color: $color-danger;
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-large;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox-container input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border: 2px solid $background-color-inverse-softer;
|
||||
background-color: $background-color-base;
|
||||
border-radius: $border-radius-x-large;
|
||||
}
|
||||
|
||||
.checkbox-container:hover input ~ .checkmark {
|
||||
background-color: $background-color-softest;
|
||||
}
|
||||
|
||||
.checkbox-container input:checked ~ .checkmark {
|
||||
background-color: $background-color-danger-active;
|
||||
}
|
||||
|
||||
.checkmark:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-container input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkbox-container .checkmark:after {
|
||||
left: 6px;
|
||||
top: 3px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid $background-color-base;
|
||||
border-width: 0 $border-size-large $border-size-large 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.enable-deletion-input input:focus {
|
||||
border-color: $border-color-danger;
|
||||
}
|
||||
|
||||
.delete-input-label {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
|
||||
b.is-danger {
|
||||
color: $text-color-danger;
|
||||
}
|
||||
|
||||
.delete-data-footer {
|
||||
border-top: $border-size-base solid $border-color-softest;
|
||||
background-color: $background-color-danger-inverse;
|
||||
}
|
||||
|
||||
#delete-user-account-warning {
|
||||
background-color: $background-color-danger-inverse;
|
||||
border-left: $border-size-x-large solid $background-color-danger-active;
|
||||
color: $text-color-danger;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
border-radius: $border-radius-x-large;
|
||||
}
|
||||
</style>
|
||||
@ -12,9 +12,7 @@
|
||||
@{{ user.slug }}
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="suggestion-list__item is-empty">
|
||||
No users found
|
||||
</div>
|
||||
<div v-else class="suggestion-list__item is-empty">No users found</div>
|
||||
</div>
|
||||
|
||||
<editor-menu-bubble :editor="editor">
|
||||
@ -175,6 +173,7 @@ import {
|
||||
History,
|
||||
} from 'tiptap-extensions'
|
||||
import Mention from './nodes/Mention.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
let throttleInputEvent
|
||||
|
||||
@ -212,7 +211,7 @@ export default {
|
||||
new ListItem(),
|
||||
new Placeholder({
|
||||
emptyNodeClass: 'is-empty',
|
||||
emptyNodeText: this.$t('editor.placeholder'),
|
||||
emptyNodeText: this.placeholder || this.$t('editor.placeholder'),
|
||||
}),
|
||||
new History(),
|
||||
new Mention({
|
||||
@ -297,6 +296,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ placeholder: 'editor/placeholder' }),
|
||||
hasResults() {
|
||||
return this.filteredUsers.length
|
||||
},
|
||||
@ -316,19 +316,20 @@ export default {
|
||||
this.editor.setContent(content)
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$root.$on('changeLanguage', () => {
|
||||
this.changePlaceHolderText()
|
||||
})
|
||||
placeholder: {
|
||||
immediate: true,
|
||||
handler: function(val) {
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
this.editor.extensions.options.placeholder.emptyNodeText = val
|
||||
},
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
methods: {
|
||||
changePlaceHolderText() {
|
||||
this.editor.extensions.options.placeholder.emptyNodeText = this.$t('editor.placeholder')
|
||||
},
|
||||
// navigate to the previous item
|
||||
// if it's the first item, navigate to the last one
|
||||
upHandler() {
|
||||
|
||||
@ -1,31 +1,43 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Editor from './'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('Editor.vue', () => {
|
||||
let wrapper
|
||||
let propsData
|
||||
let mocks
|
||||
let getters
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
mocks = {
|
||||
$t: () => {},
|
||||
}
|
||||
getters = {
|
||||
'editor/placeholder': () => {
|
||||
return 'some cool placeholder'
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return (wrapper = mount(Editor, {
|
||||
mocks,
|
||||
propsData,
|
||||
localVue,
|
||||
sync: false,
|
||||
stubs: { transition: false },
|
||||
store,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -43,5 +55,13 @@ describe('Editor.vue', () => {
|
||||
expect(wrapper.find('.ProseMirror').text()).toContain('I am a piece of text')
|
||||
})
|
||||
})
|
||||
|
||||
describe('uses the placeholder', () => {
|
||||
it('from the store', () => {
|
||||
expect(wrapper.vm.editor.extensions.options.placeholder.emptyNodeText).toEqual(
|
||||
'some cool placeholder',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,13 +2,16 @@
|
||||
<ds-card>
|
||||
<ds-flex>
|
||||
<ds-flex-item class="filter-menu-title">
|
||||
<ds-heading size="h3">
|
||||
{{ $t('filter-menu.title') }}
|
||||
</ds-heading>
|
||||
<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"
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<img v-bind="imageProps" :src="imageSrc" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
imageProps: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
imageSrc() {
|
||||
const src = this.imageProps.src
|
||||
return src.startsWith('/') ? src.replace('/', '/api/') : src
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,39 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Image from '.'
|
||||
|
||||
describe('Image', () => {
|
||||
let propsData = { imageProps: { class: 'hc-badge', src: '' } }
|
||||
|
||||
const Wrapper = () => {
|
||||
return shallowMount(Image, { propsData })
|
||||
}
|
||||
|
||||
it('renders', () => {
|
||||
expect(Wrapper().is('img')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes properties down to `img`', () => {
|
||||
expect(Wrapper().classes()).toEqual(['hc-badge'])
|
||||
})
|
||||
|
||||
describe('given a relative `src`', () => {
|
||||
beforeEach(() => {
|
||||
propsData.imageProps.src = '/img/badges/fundraisingbox_de_airship.svg'
|
||||
})
|
||||
|
||||
it('adds a prefix to load the image from the backend', () => {
|
||||
expect(Wrapper().attributes('src')).toBe('/api/img/badges/fundraisingbox_de_airship.svg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('given an absolute `src`', () => {
|
||||
beforeEach(() => {
|
||||
propsData.imageProps.src = 'http://lorempixel.com/640/480/animals'
|
||||
})
|
||||
|
||||
it('keeps the URL as is', () => {
|
||||
// e.g. our seeds have absolute image URLs
|
||||
expect(Wrapper().attributes('src')).toBe('http://lorempixel.com/640/480/animals')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ds-space margin-top="large" style="text-align: center">
|
||||
<ds-space class="load-more" margin-top="large" style="text-align: center">
|
||||
<ds-button :loading="loading" icon="arrow-down" ghost @click="$emit('click')">
|
||||
{{ $t('actions.loadMore') }}
|
||||
</ds-button>
|
||||
|
||||
68
webapp/components/LocaleSwitch/LocaleSwitch.spec.js
Normal file
68
webapp/components/LocaleSwitch/LocaleSwitch.spec.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import LocaleSwitch from './LocaleSwitch.vue'
|
||||
import { mutations } from '~/store/editor'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(VTooltip)
|
||||
|
||||
describe('LocaleSwitch.vue', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
let computed
|
||||
let deutschLanguageItem
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$i18n: {
|
||||
locale: () => 'de',
|
||||
set: jest.fn(),
|
||||
},
|
||||
$t: jest.fn(),
|
||||
setPlaceholderText: jest.fn(),
|
||||
}
|
||||
computed = {
|
||||
current: () => {
|
||||
return { code: 'en' }
|
||||
},
|
||||
routes: () => {
|
||||
return [
|
||||
{
|
||||
name: 'English',
|
||||
path: 'en',
|
||||
},
|
||||
{
|
||||
name: 'Deutsch',
|
||||
path: 'de',
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const store = new Vuex.Store({
|
||||
mutations: {
|
||||
'editor/SET_PLACEHOLDER_TEXT': mutations.SET_PLACEHOLDER_TEXT,
|
||||
},
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(LocaleSwitch, { mocks, localVue, store, computed })
|
||||
}
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('.locale-menu').trigger('click')
|
||||
deutschLanguageItem = wrapper.findAll('li').at(1)
|
||||
deutschLanguageItem.trigger('click')
|
||||
})
|
||||
|
||||
it("changes a user's locale", () => {
|
||||
expect(mocks.$i18n.set).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -36,6 +36,7 @@
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import find from 'lodash/find'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import { mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -65,10 +66,11 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({ setPlaceholderText: 'editor/SET_PLACEHOLDER_TEXT' }),
|
||||
changeLanguage(locale, toggleMenu) {
|
||||
this.$i18n.set(locale)
|
||||
toggleMenu()
|
||||
this.$root.$emit('changeLanguage')
|
||||
this.setPlaceholderText(this.$t('editor.placeholder'))
|
||||
},
|
||||
matcher(locale) {
|
||||
return locale === this.$i18n.locale()
|
||||
@ -1,6 +1,6 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import Modal from './Modal.vue'
|
||||
import DeleteModal from './Modal/DeleteModal.vue'
|
||||
import ConfirmModal from './Modal/ConfirmModal.vue'
|
||||
import DisableModal from './Modal/DisableModal.vue'
|
||||
import ReportModal from './Modal/ReportModal.vue'
|
||||
import Vuex from 'vuex'
|
||||
@ -60,7 +60,7 @@ describe('Modal.vue', () => {
|
||||
|
||||
it('initially empty', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.contains(DeleteModal)).toBe(false)
|
||||
expect(wrapper.contains(ConfirmModal)).toBe(false)
|
||||
expect(wrapper.contains(DisableModal)).toBe(false)
|
||||
expect(wrapper.contains(ReportModal)).toBe(false)
|
||||
})
|
||||
@ -75,10 +75,6 @@ describe('Modal.vue', () => {
|
||||
id: 'c456',
|
||||
title: 'some title',
|
||||
},
|
||||
callbacks: {
|
||||
confirm: null,
|
||||
cancel: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -93,10 +89,6 @@ describe('Modal.vue', () => {
|
||||
type: 'contribution',
|
||||
name: 'some title',
|
||||
id: 'c456',
|
||||
callbacks: {
|
||||
confirm: null,
|
||||
cancel: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@ -117,20 +109,12 @@ describe('Modal.vue', () => {
|
||||
name: 'Author name',
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
confirm: null,
|
||||
cancel: null,
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
type: 'comment',
|
||||
name: 'Author name',
|
||||
id: 'c456',
|
||||
callbacks: {
|
||||
confirm: null,
|
||||
cancel: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@ -140,20 +124,12 @@ describe('Modal.vue', () => {
|
||||
resource: {
|
||||
id: 'c456',
|
||||
},
|
||||
callbacks: {
|
||||
confirm: null,
|
||||
cancel: null,
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(DisableModal).props()).toEqual({
|
||||
type: 'comment',
|
||||
name: '',
|
||||
id: 'c456',
|
||||
callbacks: {
|
||||
confirm: null,
|
||||
cancel: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
:id="data.resource.id"
|
||||
:type="data.type"
|
||||
:name="name"
|
||||
:callbacks="data.callbacks"
|
||||
@close="close"
|
||||
/>
|
||||
<release-modal
|
||||
@ -21,22 +20,21 @@
|
||||
:id="data.resource.id"
|
||||
:type="data.type"
|
||||
:name="name"
|
||||
:callbacks="data.callbacks"
|
||||
@close="close"
|
||||
/>
|
||||
<delete-modal
|
||||
<confirm-modal
|
||||
v-if="open === 'delete'"
|
||||
:id="data.resource.id"
|
||||
:type="data.type"
|
||||
:name="name"
|
||||
:callbacks="data.callbacks"
|
||||
:modalData="data.modalsData.delete"
|
||||
@close="close"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeleteModal from '~/components/Modal/DeleteModal'
|
||||
import ConfirmModal from '~/components/Modal/ConfirmModal'
|
||||
import DisableModal from '~/components/Modal/DisableModal'
|
||||
import ReleaseModal from '~/components/ReleaseModal/ReleaseModal.vue'
|
||||
import ReportModal from '~/components/Modal/ReportModal'
|
||||
@ -48,7 +46,7 @@ export default {
|
||||
DisableModal,
|
||||
ReleaseModal,
|
||||
ReportModal,
|
||||
DeleteModal,
|
||||
ConfirmModal,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@ -63,7 +61,7 @@ export default {
|
||||
switch (this.data.type) {
|
||||
case 'user':
|
||||
return name
|
||||
case 'contribution':
|
||||
case 'contribution': // REFACTORING: In ConfirmModal – Already replaced "title" by "this.menuModalsData.delete.messageParams".
|
||||
return title
|
||||
case 'comment':
|
||||
return author && author.name
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
|
||||
import DeleteModal from './DeleteModal.vue'
|
||||
import Vuex from 'vuex'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import ConfirmModal from './ConfirmModal.vue'
|
||||
import { postMenuModalsData } from '~/components/utils/PostHelpers'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('DeleteModal.vue', () => {
|
||||
describe('ConfirmModal.vue', () => {
|
||||
let Wrapper
|
||||
let wrapper
|
||||
let propsData
|
||||
let mocks
|
||||
const postName = 'It is a post'
|
||||
const confirmCallback = jest.fn()
|
||||
const cancelCallback = jest.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
type: 'contribution',
|
||||
id: 'p23',
|
||||
name: 'It is a post',
|
||||
callbacks: {
|
||||
confirm: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
},
|
||||
name: postName,
|
||||
modalData: postMenuModalsData(postName, confirmCallback, cancelCallback).delete,
|
||||
}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
@ -32,9 +33,13 @@ describe('DeleteModal.vue', () => {
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
Wrapper = () => {
|
||||
return shallowMount(DeleteModal, {
|
||||
return shallowMount(ConfirmModal, {
|
||||
propsData,
|
||||
mocks,
|
||||
localVue,
|
||||
@ -61,7 +66,7 @@ describe('DeleteModal.vue', () => {
|
||||
...propsData,
|
||||
type: 'contribution',
|
||||
id: 'p23',
|
||||
name: 'It is a post',
|
||||
name: postName,
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
@ -72,32 +77,7 @@ describe('DeleteModal.vue', () => {
|
||||
[
|
||||
'delete.contribution.message',
|
||||
{
|
||||
name: 'It is a post',
|
||||
},
|
||||
],
|
||||
]
|
||||
expect(calls).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a comment', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
...propsData,
|
||||
type: 'comment',
|
||||
id: 'c4',
|
||||
name: 'It is the user of the comment',
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('mentions comments user name', () => {
|
||||
const calls = mocks.$t.mock.calls
|
||||
const expected = [
|
||||
[
|
||||
'delete.comment.message',
|
||||
{
|
||||
name: 'It is the user of the comment',
|
||||
name: postName,
|
||||
},
|
||||
],
|
||||
]
|
||||
@ -108,7 +88,7 @@ describe('DeleteModal.vue', () => {
|
||||
|
||||
describe('mount', () => {
|
||||
Wrapper = () => {
|
||||
return mount(DeleteModal, {
|
||||
return mount(ConfirmModal, {
|
||||
propsData,
|
||||
mocks,
|
||||
localVue,
|
||||
@ -135,7 +115,7 @@ describe('DeleteModal.vue', () => {
|
||||
})
|
||||
|
||||
it('does call the cancel callback', () => {
|
||||
expect(propsData.callbacks.cancel).toHaveBeenCalledTimes(1)
|
||||
expect(cancelCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('emits "close"', () => {
|
||||
@ -161,10 +141,11 @@ describe('DeleteModal.vue', () => {
|
||||
})
|
||||
|
||||
it('does call the confirm callback', () => {
|
||||
expect(propsData.callbacks.confirm).toHaveBeenCalledTimes(1)
|
||||
expect(confirmCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('emits close', () => {
|
||||
expect(wrapper.emitted().close).toBeTruthy()
|
||||
|
||||
it('emits "close"', () => {
|
||||
expect(wrapper.emitted().close).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('resets success', () => {
|
||||
@ -10,10 +10,18 @@
|
||||
<p v-html="message" />
|
||||
|
||||
<template slot="footer">
|
||||
<ds-button class="cancel" icon="close" @click="cancel">{{ $t('delete.cancel') }}</ds-button>
|
||||
<ds-button class="cancel" :icon="modalData.buttons.cancel.icon" @click="cancel">
|
||||
{{ $t(modalData.buttons.cancel.textIdent) }}
|
||||
</ds-button>
|
||||
|
||||
<ds-button danger class="confirm" icon="trash" :loading="loading" @click="confirm">
|
||||
{{ $t('delete.submit') }}
|
||||
<ds-button
|
||||
:danger="modalData.buttons.confirm.danger"
|
||||
class="confirm"
|
||||
:icon="modalData.buttons.confirm.icon"
|
||||
:loading="loading"
|
||||
@click="confirm"
|
||||
>
|
||||
{{ $t(modalData.buttons.confirm.textIdent) }}
|
||||
</ds-button>
|
||||
</template>
|
||||
</ds-modal>
|
||||
@ -23,14 +31,14 @@
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
name: 'DeleteModal',
|
||||
name: 'ConfirmModal',
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
},
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
type: { type: String, required: true },
|
||||
callbacks: { type: Object, required: true },
|
||||
modalData: { type: Object, required: true },
|
||||
id: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
@ -42,18 +50,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.$t(`delete.${this.type}.title`)
|
||||
return this.$t(this.modalData.titleIdent)
|
||||
},
|
||||
message() {
|
||||
const name = this.$filters.truncate(this.name, 30)
|
||||
return this.$t(`delete.${this.type}.message`, { name })
|
||||
return this.$t(this.modalData.messageIdent, this.modalData.messageParams)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async cancel() {
|
||||
if (this.callbacks.cancel) {
|
||||
await this.callbacks.cancel()
|
||||
}
|
||||
await this.modalData.buttons.cancel.callback()
|
||||
this.isOpen = false
|
||||
setTimeout(() => {
|
||||
this.$emit('close')
|
||||
@ -62,9 +67,7 @@ export default {
|
||||
async confirm() {
|
||||
this.loading = true
|
||||
try {
|
||||
if (this.callbacks.confirm) {
|
||||
await this.callbacks.confirm()
|
||||
}
|
||||
await this.modalData.buttons.confirm.callback()
|
||||
this.success = true
|
||||
setTimeout(() => {
|
||||
this.isOpen = false
|
||||
@ -16,10 +16,6 @@ describe('DisableModal.vue', () => {
|
||||
type: 'contribution',
|
||||
id: 'c42',
|
||||
name: 'blah',
|
||||
callbacks: {
|
||||
confirm: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
},
|
||||
}
|
||||
mocks = {
|
||||
$filters: {
|
||||
@ -33,8 +29,12 @@ describe('DisableModal.vue', () => {
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ enable: 'u4711' })
|
||||
.mockRejectedValue({ message: 'Not Authorised!' }),
|
||||
.mockResolvedValueOnce({
|
||||
enable: 'u4711',
|
||||
})
|
||||
.mockRejectedValue({
|
||||
message: 'Not Authorised!',
|
||||
}),
|
||||
},
|
||||
location: {
|
||||
reload: jest.fn(),
|
||||
|
||||
@ -21,7 +21,6 @@ export default {
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
type: { type: String, required: true },
|
||||
callbacks: { type: Object, required: true },
|
||||
id: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
@ -42,9 +41,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async cancel() {
|
||||
if (this.callbacks.cancel) {
|
||||
await this.callbacks.cancel()
|
||||
}
|
||||
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
|
||||
// await this.modalData.buttons.cancel.callback()
|
||||
this.isOpen = false
|
||||
setTimeout(() => {
|
||||
this.$emit('close')
|
||||
@ -52,9 +50,8 @@ export default {
|
||||
},
|
||||
async confirm() {
|
||||
try {
|
||||
if (this.callbacks.confirm) {
|
||||
await this.callbacks.confirm()
|
||||
}
|
||||
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
|
||||
// await this.modalData.buttons.confirm.callback()
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
|
||||
@ -17,10 +17,6 @@ describe('ReportModal.vue', () => {
|
||||
propsData = {
|
||||
type: 'contribution',
|
||||
id: 'c43',
|
||||
callbacks: {
|
||||
confirm: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
},
|
||||
}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
|
||||
@ -10,9 +10,7 @@
|
||||
<p v-html="message" />
|
||||
|
||||
<template slot="footer">
|
||||
<ds-button class="cancel" icon="close" @click="cancel">
|
||||
{{ $t('report.cancel') }}
|
||||
</ds-button>
|
||||
<ds-button class="cancel" icon="close" @click="cancel">{{ $t('report.cancel') }}</ds-button>
|
||||
|
||||
<ds-button
|
||||
danger
|
||||
@ -39,7 +37,6 @@ export default {
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
type: { type: String, required: true },
|
||||
callbacks: { type: Object, required: true },
|
||||
id: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
@ -60,9 +57,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async cancel() {
|
||||
if (this.callbacks.cancel) {
|
||||
await this.callbacks.cancel()
|
||||
}
|
||||
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
|
||||
// await this.modalData.buttons.cancel.callback()
|
||||
this.isOpen = false
|
||||
setTimeout(() => {
|
||||
this.$emit('close')
|
||||
@ -71,9 +67,8 @@ export default {
|
||||
async confirm() {
|
||||
this.loading = true
|
||||
try {
|
||||
if (this.callbacks.confirm) {
|
||||
await this.callbacks.confirm()
|
||||
}
|
||||
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
|
||||
// await this.modalData.buttons.confirm.callback()
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import ChangePassword from './Change.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
|
||||
describe('ChangePassword.vue', () => {
|
||||
let mocks
|
||||
|
||||
130
webapp/components/PostCard/index.spec.js
Normal file
130
webapp/components/PostCard/index.spec.js
Normal file
@ -0,0 +1,130 @@
|
||||
import { config, shallowMount, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
import PostCard from '.'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
|
||||
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
|
||||
describe('PostCard', () => {
|
||||
let store
|
||||
let stubs
|
||||
let mocks
|
||||
let propsData
|
||||
let getters
|
||||
let Wrapper
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
post: {
|
||||
id: 'p23',
|
||||
name: 'It is a post',
|
||||
author: {
|
||||
id: 'u1',
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
}
|
||||
store = new Vuex.Store({
|
||||
getters: {
|
||||
'auth/user': () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
stubs = {
|
||||
NuxtLink: RouterLinkStub,
|
||||
}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
$apollo: {
|
||||
mutate: jest.fn().mockResolvedValue(),
|
||||
},
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
Wrapper = () => {
|
||||
return shallowMount(PostCard, {
|
||||
store,
|
||||
propsData,
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
describe('test Post callbacks', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('deletion of Post from Page by invoking "deletePostCallback()"', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.deletePostCallback()
|
||||
})
|
||||
|
||||
describe('after timeout', () => {
|
||||
beforeEach(jest.runAllTimers)
|
||||
|
||||
it('does call mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('mutation is successful', () => {
|
||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('emits "removePostFromList"', () => {
|
||||
expect(wrapper.emitted().removePostFromList).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return mount(PostCard, {
|
||||
stubs,
|
||||
mocks,
|
||||
propsData,
|
||||
store,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
describe('given a post', () => {
|
||||
beforeEach(() => {
|
||||
propsData.post = {
|
||||
title: "It's a title",
|
||||
}
|
||||
})
|
||||
|
||||
it('renders title', () => {
|
||||
expect(Wrapper().text()).toContain("It's a title")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<ds-flex-item :width="width">
|
||||
<ds-card :image="post.image" :class="{ 'post-card': true, 'disabled-content': post.disabled }">
|
||||
<ds-card
|
||||
:image="post.image | proxyApiUrl"
|
||||
:class="{ 'post-card': true, 'disabled-content': post.disabled }"
|
||||
>
|
||||
<!-- Post Link Target -->
|
||||
<nuxt-link
|
||||
class="post-link"
|
||||
@ -18,9 +21,7 @@
|
||||
</div>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Post Title -->
|
||||
<ds-heading tag="h3" no-margin>
|
||||
{{ post.title }}
|
||||
</ds-heading>
|
||||
<ds-heading tag="h3" no-margin>{{ post.title }}</ds-heading>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Post Content Excerpt -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
@ -55,7 +56,7 @@
|
||||
<content-menu
|
||||
resource-type="contribution"
|
||||
:resource="post"
|
||||
:callbacks="{ confirm: deletePostCallback, cancel: null }"
|
||||
:modalsData="menuModalsData"
|
||||
:is-owner="isAuthor"
|
||||
/>
|
||||
</div>
|
||||
@ -72,7 +73,7 @@ import HcCategory from '~/components/Category'
|
||||
import HcRibbon from '~/components/Ribbon'
|
||||
// import { randomBytes } from 'crypto'
|
||||
import { mapGetters } from 'vuex'
|
||||
import PostMutationHelpers from '~/mixins/PostMutationHelpers'
|
||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||
|
||||
export default {
|
||||
name: 'HcPostCard',
|
||||
@ -82,7 +83,6 @@ export default {
|
||||
HcRibbon,
|
||||
ContentMenu,
|
||||
},
|
||||
mixins: [PostMutationHelpers],
|
||||
props: {
|
||||
post: {
|
||||
type: Object,
|
||||
@ -105,6 +105,24 @@ export default {
|
||||
if (!author) return false
|
||||
return this.user.id === this.post.author.id
|
||||
},
|
||||
menuModalsData() {
|
||||
return postMenuModalsData(
|
||||
// "this.post" may not always be defined at the beginning …
|
||||
this.post ? this.$filters.truncate(this.post.title, 30) : '',
|
||||
this.deletePostCallback,
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deletePostCallback() {
|
||||
try {
|
||||
await this.$apollo.mutate(deletePostMutation(this.post.id))
|
||||
this.$toast.success(this.$t('delete.contribution.success'))
|
||||
this.$emit('removePostFromList')
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
import { config, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
||||
import PostCard from '.'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
|
||||
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
|
||||
describe('PostCard', () => {
|
||||
let stubs
|
||||
let mocks
|
||||
let propsData
|
||||
let getters
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
stubs = {
|
||||
NuxtLink: RouterLinkStub,
|
||||
}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return mount(PostCard, {
|
||||
stubs,
|
||||
mocks,
|
||||
propsData,
|
||||
store,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
describe('given a post', () => {
|
||||
beforeEach(() => {
|
||||
propsData.post = {
|
||||
title: "It's a title",
|
||||
}
|
||||
})
|
||||
|
||||
it('renders title', () => {
|
||||
expect(Wrapper().text()).toContain("It's a title")
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -40,6 +40,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
|
||||
// await this.modalData.buttons.cancel.callback()
|
||||
this.isOpen = false
|
||||
setTimeout(() => {
|
||||
this.$emit('close')
|
||||
@ -47,6 +49,8 @@ export default {
|
||||
},
|
||||
async confirm() {
|
||||
try {
|
||||
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
|
||||
// await this.modalData.buttons.confirm.callback()
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
|
||||
@ -2,6 +2,7 @@ import { mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
||||
import User from './index'
|
||||
import Vuex from 'vuex'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
@ -11,6 +12,7 @@ const filter = jest.fn(str => str)
|
||||
localVue.use(Vuex)
|
||||
localVue.use(VTooltip)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
|
||||
localVue.filter('truncate', filter)
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { mount, createLocalVue, createWrapper } from '@vue/test-utils'
|
||||
import CommentForm from './index.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('CommentForm.vue', () => {
|
||||
@ -35,8 +36,16 @@ describe('CommentForm.vue', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const getters = {
|
||||
'editor/placeholder': () => {
|
||||
return 'some cool placeholder'
|
||||
},
|
||||
}
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(CommentForm, { mocks, localVue, propsData })
|
||||
return mount(CommentForm, { mocks, localVue, propsData, store })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -3,11 +3,13 @@ import CommentList from '.'
|
||||
import Empty from '~/components/Empty'
|
||||
import Vuex from 'vuex'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Filters)
|
||||
localVue.filter('truncate', string => string)
|
||||
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
@ -22,7 +24,9 @@ describe('CommentList.vue', () => {
|
||||
let data
|
||||
|
||||
propsData = {
|
||||
post: { id: 1 },
|
||||
post: {
|
||||
id: 1,
|
||||
},
|
||||
}
|
||||
store = new Vuex.Store({
|
||||
getters: {
|
||||
@ -33,6 +37,9 @@ describe('CommentList.vue', () => {
|
||||
})
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$filters: {
|
||||
truncate: a => a,
|
||||
},
|
||||
$apollo: {
|
||||
queries: {
|
||||
Post: {
|
||||
@ -49,13 +56,24 @@ describe('CommentList.vue', () => {
|
||||
|
||||
describe('shallowMount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(CommentList, { store, mocks, localVue, propsData, data })
|
||||
return mount(CommentList, {
|
||||
store,
|
||||
mocks,
|
||||
localVue,
|
||||
propsData,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.setData({
|
||||
comments: [{ id: 'c1', contentExcerpt: 'this is a comment' }],
|
||||
comments: [
|
||||
{
|
||||
id: 'c1',
|
||||
contentExcerpt: 'this is a comment',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
@ -68,7 +86,7 @@ describe('CommentList.vue', () => {
|
||||
})
|
||||
|
||||
it('displays comments when there are comments to display', () => {
|
||||
expect(wrapper.find('div#comments').text()).toEqual('this is a comment')
|
||||
expect(wrapper.find('div.comments').text()).toEqual('this is a comment')
|
||||
})
|
||||
|
||||
it("refetches a post's comments from the backend", () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="comments">
|
||||
<h3 style="margin-top: -10px;">
|
||||
<span>
|
||||
<ds-icon name="comments" />
|
||||
@ -16,7 +16,7 @@
|
||||
</span>
|
||||
</h3>
|
||||
<ds-space margin-bottom="large" />
|
||||
<div v-if="comments && comments.length" id="comments" class="comments">
|
||||
<div v-if="comments && comments.length" class="comments">
|
||||
<comment
|
||||
v-for="(comment, index) in comments"
|
||||
:key="comment.id"
|
||||
@ -54,6 +54,9 @@ export default {
|
||||
this.refetchPostComments()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('refetchPostComments')
|
||||
},
|
||||
methods: {
|
||||
refetchPostComments() {
|
||||
if (this.$apollo.queries.Post) {
|
||||
|
||||
35
webapp/components/utils/PostHelpers.js
Normal file
35
webapp/components/utils/PostHelpers.js
Normal file
@ -0,0 +1,35 @@
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
export function postMenuModalsData(truncatedPostName, confirmCallback, cancelCallback = () => {}) {
|
||||
return {
|
||||
delete: {
|
||||
titleIdent: 'delete.contribution.title',
|
||||
messageIdent: 'delete.contribution.message',
|
||||
messageParams: {
|
||||
name: truncatedPostName,
|
||||
},
|
||||
buttons: {
|
||||
confirm: {
|
||||
danger: true,
|
||||
icon: 'trash',
|
||||
textIdent: 'delete.submit',
|
||||
callback: confirmCallback,
|
||||
},
|
||||
cancel: {
|
||||
icon: 'close',
|
||||
textIdent: 'delete.cancel',
|
||||
callback: cancelCallback,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function deletePostMutation(postId) {
|
||||
return {
|
||||
mutation: PostMutations().DeletePost,
|
||||
variables: {
|
||||
id: postId,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,37 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default app => {
|
||||
export default () => {
|
||||
return {
|
||||
CreatePost: gql(`
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
CreatePost: gql`
|
||||
mutation($title: String!, $content: String!, $language: String) {
|
||||
CreatePost(title: $title, content: $content, language: $language) {
|
||||
id
|
||||
title
|
||||
slug
|
||||
content
|
||||
contentExcerpt
|
||||
language
|
||||
}
|
||||
}
|
||||
`),
|
||||
UpdatePost: gql(`
|
||||
mutation($id: ID!, $title: String!, $content: String!) {
|
||||
UpdatePost(id: $id, title: $title, content: $content) {
|
||||
`,
|
||||
UpdatePost: gql`
|
||||
mutation($id: ID!, $title: String!, $content: String!, $language: String) {
|
||||
UpdatePost(id: $id, title: $title, content: $content, language: $language) {
|
||||
id
|
||||
title
|
||||
slug
|
||||
content
|
||||
contentExcerpt
|
||||
language
|
||||
}
|
||||
}
|
||||
`),
|
||||
`,
|
||||
DeletePost: gql`
|
||||
mutation($id: ID!) {
|
||||
DeletePost(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
38
webapp/graphql/UserProfile/Post.js
Normal file
38
webapp/graphql/UserProfile/Post.js
Normal file
@ -0,0 +1,38 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql(`
|
||||
query Post($filter: _PostFilter, $first: Int, $offset: Int) {
|
||||
Post(filter: $filter, first: $first, offset: $offset, orderBy: createdAt_desc) {
|
||||
id
|
||||
slug
|
||||
title
|
||||
contentExcerpt
|
||||
shoutedCount
|
||||
commentsCount
|
||||
deleted
|
||||
image
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
categories {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
author {
|
||||
id
|
||||
slug
|
||||
avatar
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
location {
|
||||
name: name${lang}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default app => {
|
||||
const lang = app.$i18n.locale().toUpperCase()
|
||||
export default i18n => {
|
||||
const lang = i18n.locale().toUpperCase()
|
||||
return gql(`
|
||||
query User($slug: String!, $first: Int, $offset: Int) {
|
||||
User(slug: $slug) {
|
||||
query User($id: ID!) {
|
||||
User(id: $id) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
@ -24,7 +24,7 @@ export default app => {
|
||||
}
|
||||
badgesCount
|
||||
shoutedCount
|
||||
commentsCount
|
||||
commentedCount
|
||||
followingCount
|
||||
following(first: 7) {
|
||||
id
|
||||
@ -69,35 +69,6 @@ export default app => {
|
||||
}
|
||||
}
|
||||
contributionsCount
|
||||
contributions(first: $first, offset: $offset, orderBy: createdAt_desc) {
|
||||
id
|
||||
slug
|
||||
title
|
||||
contentExcerpt
|
||||
shoutedCount
|
||||
commentsCount
|
||||
deleted
|
||||
image
|
||||
createdAt
|
||||
disabled
|
||||
deleted
|
||||
categories {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
author {
|
||||
id
|
||||
slug
|
||||
avatar
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
location {
|
||||
name: name${lang}
|
||||
}
|
||||
}
|
||||
}
|
||||
socialMedia {
|
||||
id
|
||||
url
|
||||
@ -92,7 +92,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import SearchInput from '~/components/SearchInput.vue'
|
||||
import Modal from '~/components/Modal'
|
||||
import NotificationMenu from '~/components/notifications/NotificationMenu'
|
||||
|
||||
@ -19,9 +19,9 @@
|
||||
"profile": {
|
||||
"name": "Mein Profil",
|
||||
"memberSince": "Mitglied seit",
|
||||
"follow": "Folgen",
|
||||
"followers": "Folgen",
|
||||
"following": "Folgt",
|
||||
"follow": "abonnieren",
|
||||
"followers": "Abonnenten",
|
||||
"following": "abonniert",
|
||||
"shouted": "Empfohlen",
|
||||
"commented": "Kommentiert",
|
||||
"userAnonym": "Anonymus",
|
||||
@ -82,8 +82,14 @@
|
||||
"download": {
|
||||
"name": "Daten herunterladen"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Konto löschen"
|
||||
"deleteUserAccount": {
|
||||
"name": "Daten löschen",
|
||||
"contributionsCount": "Meine {count} Beiträge löschen",
|
||||
"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",
|
||||
"pleaseConfirm": "<b class='is-danger'>Zerstörerische Aktion!</b> Gib <b>{confirm}</b> ein, um zu bestätigen."
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Meine Organisationen"
|
||||
@ -100,9 +106,9 @@
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Systemverwaltung",
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Startzentrale",
|
||||
"name": "Dashboard",
|
||||
"users": "Benutzer",
|
||||
"posts": "Beiträge",
|
||||
"comments": "Kommentare",
|
||||
@ -111,7 +117,7 @@
|
||||
"projects": "Projekte",
|
||||
"invites": "Einladungen",
|
||||
"follows": "Folgen",
|
||||
"shouts": "Shouts"
|
||||
"shouts": "Empfehlungen"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisationen"
|
||||
@ -172,11 +178,6 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"your": {
|
||||
"post": "Dein Beitrag ::: Deine Beiträge",
|
||||
"comment": "Dein Kommentar ::: Deine Kommentare",
|
||||
"shout": "Deine Empfehlung ::: Deine Empfehlungen"
|
||||
},
|
||||
"post": "Beitrag ::: Beiträge",
|
||||
"comment": "Kommentar ::: Kommentare",
|
||||
"letsTalk": "Miteinander reden",
|
||||
@ -244,7 +245,7 @@
|
||||
"comment": {
|
||||
"title": "Lösche Kommentar",
|
||||
"type": "Comment",
|
||||
"message": "Bist du sicher, dass du den Kommentar von \"<b>{name}</b>\" löschen möchtest?",
|
||||
"message": "Bist du sicher, dass du den Kommentar \"<b>{name}</b>\" löschen möchtest?",
|
||||
"success": "Kommentar erfolgreich gelöscht!"
|
||||
}
|
||||
},
|
||||
@ -270,7 +271,7 @@
|
||||
},
|
||||
"followButton": {
|
||||
"follow": "Folgen",
|
||||
"following": "Folge Ich"
|
||||
"following": "Folge ich"
|
||||
},
|
||||
"shoutButton": {
|
||||
"shouted": "empfohlen"
|
||||
@ -300,5 +301,13 @@
|
||||
"avatar": {
|
||||
"submitted": "Upload erfolgreich"
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
"newPost": "Erstelle einen neuen Beitrag",
|
||||
"filterFollow": "Beiträge filtern von Usern denen ich folge",
|
||||
"filterALL": "Alle Beiträge anzeigen",
|
||||
"success": "Gespeichert!",
|
||||
"languageSelectLabel": "Sprache"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
"follow": "Follow",
|
||||
"followers": "Followers",
|
||||
"following": "Following",
|
||||
"shouted": "Shouted",
|
||||
"shouted": "Recommended",
|
||||
"commented": "Commented",
|
||||
"userAnonym": "Anonymous",
|
||||
"socialMedia": "Where else can I find",
|
||||
@ -38,7 +38,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"mentioned": "has mentioned you in a post"
|
||||
"mentioned": "mentioned you in a post"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
@ -52,7 +52,7 @@
|
||||
"name": "Your data",
|
||||
"labelName": "Your Name",
|
||||
"namePlaceholder": "Femanon Funny",
|
||||
"labelCity": "Your City or Region",
|
||||
"labelCity": "Su ciudad o región",
|
||||
"labelBio": "About You",
|
||||
"success": "Your data was successfully updated!"
|
||||
},
|
||||
@ -82,8 +82,14 @@
|
||||
"download": {
|
||||
"name": "Download Data"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Delete Account"
|
||||
"deleteUserAccount": {
|
||||
"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",
|
||||
"pleaseConfirm": "<b class='is-danger'>Destructive action!</b> Type <b>{confirm}</b> to confirm"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "My Organizations"
|
||||
@ -111,7 +117,7 @@
|
||||
"projects": "Projects",
|
||||
"invites": "Invites",
|
||||
"follows": "Follows",
|
||||
"shouts": "Shouts"
|
||||
"shouts": "Recommended"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizations"
|
||||
@ -172,11 +178,6 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"your": {
|
||||
"post": "Your Post ::: Your Posts",
|
||||
"comment": "Your Comment ::: Your Comments",
|
||||
"shout": "Your Shout ::: Your Shouts"
|
||||
},
|
||||
"post": "Post ::: Posts",
|
||||
"comment": "Comment ::: Comments",
|
||||
"letsTalk": "Let`s Talk",
|
||||
@ -244,7 +245,7 @@
|
||||
"comment": {
|
||||
"title": "Delete Comment",
|
||||
"type": "Comment",
|
||||
"message": "Do you really want to delete the comment from \"<b>{name}</b>\"?",
|
||||
"message": "Do you really want to delete the comment \"<b>{name}</b>\"?",
|
||||
"success": "Comment successfully deleted!"
|
||||
}
|
||||
},
|
||||
@ -299,5 +300,12 @@
|
||||
"avatar": {
|
||||
"submitted": "Upload successful"
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
"newPost": "Create a new Post",
|
||||
"filterFollow": "Filter contributions from users I follow",
|
||||
"filterALL": "View all contributions",
|
||||
"success": "Saved!",
|
||||
"languageSelectLabel": "Language"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
"filter-menu": {
|
||||
"title": "Su burbuja de filtro"
|
||||
},
|
||||
"login": {
|
||||
"copy": "Si ya tiene una cuenta de Human Connection, inicie sesión aquí.",
|
||||
"login": "Iniciar sesión",
|
||||
@ -6,39 +9,85 @@
|
||||
"email": "Tu correo electrónico",
|
||||
"password": "Tu contraseña",
|
||||
"moreInfo": "¿Qué es Human Connection?",
|
||||
"moreInfoURL": "https://human-connection.org/es/",
|
||||
"moreInfoHint": "a la página de presentación",
|
||||
"hello": "Hola"
|
||||
},
|
||||
"editor": {
|
||||
"placeholder": "Write something inspiring..."
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mi perfil",
|
||||
"name": "Mi Perfil",
|
||||
"memberSince": "Miembro desde",
|
||||
"follow": "Seguir",
|
||||
"followers": "Seguidores",
|
||||
"following": "Siguiendo",
|
||||
"shouted": "Gritar",
|
||||
"commented": "Comentado"
|
||||
"shouted": "Recomendado",
|
||||
"commented": "Comentado",
|
||||
"userAnonym": "Anónimo",
|
||||
"socialMedia": "¿Dónde más puedo encontrar"
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"mentioned": "te ha mencionado en un post"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Buscar",
|
||||
"hint": "¿Qué estás buscando?",
|
||||
"failed": "no encontró nada"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Configuración",
|
||||
"data": {
|
||||
"name": "Sus datos"
|
||||
"name": "Sus datos",
|
||||
"labelName": "Su nombre",
|
||||
"namePlaceholder": "Femanon Funny",
|
||||
"labelCity": "Your City or Region",
|
||||
"labelBio": "Acerca de usted",
|
||||
"success": "Sus datos han sido actualizados con éxito!"
|
||||
},
|
||||
"security": {
|
||||
"name": "Seguridad"
|
||||
"name": "Seguridad",
|
||||
"change-password": {
|
||||
"button": "Cambiar contraseña",
|
||||
"success": "Contraseña cambiada con éxito!",
|
||||
"label-old-password": "Su contraseña antigua",
|
||||
"label-new-password": "Su nueva contraseña",
|
||||
"label-new-password-confirm": "Confirm new password",
|
||||
"message-old-password-required": "Ingrese su contraseña anterior",
|
||||
"message-new-password-required": "Introduzca una nueva contraseña",
|
||||
"message-new-password-confirm-required": "Confirme su nueva contraseña",
|
||||
"message-new-password-missmatch": "Vuelva a escribir la misma contraseña",
|
||||
"passwordSecurity": "Seguridad de la contraseña",
|
||||
"passwordStrength0": "Contraseña muy insegura",
|
||||
"passwordStrength1": "Contraseña insegura",
|
||||
"passwordStrength2": "Contraseña mediocre",
|
||||
"passwordStrength3": "Contraseña segura",
|
||||
"passwordStrength4": "Contraseña muy sólida"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invita"
|
||||
"name": "invitaciones"
|
||||
},
|
||||
"download": {
|
||||
"name": "Descargar datos"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Borrar cuenta"
|
||||
"name": "Eliminar cuenta"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mis organizaciones"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Idiomas"
|
||||
"name": "idiomas"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Medios de comunicación social",
|
||||
"placeholder": "Agregar una URL de Social-Media",
|
||||
"submit": "Añadir enlace",
|
||||
"successAdd": "Social-Media agregó. Perfil actualizado!",
|
||||
"successDelete": "Social-Media borrado. Perfil actualizado!"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
@ -53,7 +102,7 @@
|
||||
"projects": "Proyectos",
|
||||
"invites": "Invita",
|
||||
"follows": "Sigue",
|
||||
"shouts": "Gritos"
|
||||
"shouts": "Recomendado"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizaciones"
|
||||
@ -105,6 +154,11 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"your": {
|
||||
"post": "Your Post ::: Your Posts",
|
||||
"comment": "Your Comment ::: Your Comments",
|
||||
"shout": "Your Shout ::: Your Shouts"
|
||||
},
|
||||
"post": "Mensaje ::: Mensajes",
|
||||
"comment": "Comentario ::: Comentarios",
|
||||
"letsTalk": "Hablemos",
|
||||
@ -119,6 +173,113 @@
|
||||
"tag": "Etiqueta ::: Etiquetas",
|
||||
"name": "Nombre",
|
||||
"loadMore": "cargar más",
|
||||
"loading": "cargando"
|
||||
"loading": "cargando",
|
||||
"reportContent": "Report"
|
||||
},
|
||||
"actions": {
|
||||
"loading": "cargamento",
|
||||
"loadMore": "cargar más",
|
||||
"create": "Crear",
|
||||
"save": "Guardar",
|
||||
"edit": "Edite",
|
||||
"delete": "Delete",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Moderación",
|
||||
"reports": {
|
||||
"empty": "Felicitaciones, nada que moderar.",
|
||||
"name": "Informes",
|
||||
"submitter": "comunicado por",
|
||||
"disabledBy": "desactivado por"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"submit": "Desactivar",
|
||||
"cancel": "Cancelar",
|
||||
"success": "Discapacitado con éxito",
|
||||
"user": {
|
||||
"title": "Desactivar usuario",
|
||||
"type": "Usuario",
|
||||
"message": "¿Realmente quieres deshabilitar el usuario \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Deshabilitar contribución",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente quieres deshabilitar la contribución \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Desactivar comentario",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres deshabilitar el comentario de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"submit": "Borrar",
|
||||
"cancel": "Cancelar",
|
||||
"contribution": {
|
||||
"title": "Borrar contribución",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente desea eliminar la Contribución \"<b>{name}</b>\" ?",
|
||||
"success": "Contribución eliminada con éxito!"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Eliminar comentario",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres borrar el comentario de \"<b>{name}</b>\" ?",
|
||||
"success": "Comentario eliminado con éxito!"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"submit": "Informe",
|
||||
"cancel": "Cancelar",
|
||||
"success": "Gracias por informarnos!",
|
||||
"user": {
|
||||
"title": "Usuario de informe",
|
||||
"type": "Usuario",
|
||||
"message": "¿Realmente quieres reportar al usuario \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Informe Contribución",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente quieres informar al usuario de la contribución \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Informe Comentario",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres reportar el comentario de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"followButton": {
|
||||
"follow": "Folgen",
|
||||
"following": "Folge Ich"
|
||||
},
|
||||
"shoutButton": {
|
||||
"shouted": "empfohlen"
|
||||
},
|
||||
"release": {
|
||||
"submit": "Liberación",
|
||||
"cancel": "Cancelar",
|
||||
"success": "Liberar con éxito!",
|
||||
"user": {
|
||||
"title": "Usuario de la versión ",
|
||||
"type": "Usuario",
|
||||
"message": "¿Realmente quieres liberar al usuario \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Contribución de la versión ",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente quieres liberar la contribución \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Comentario de la versión",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres liberar el comentario de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Carga con éxito"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
"filter-menu": {
|
||||
"title": "Votre bulle de filtre"
|
||||
},
|
||||
"login": {
|
||||
"copy": "Si vous avez déjà un compte human-connection, connectez-vous ici.",
|
||||
"login": "Connexion",
|
||||
@ -6,25 +9,66 @@
|
||||
"email": "Votre courriel",
|
||||
"password": "Votre mot de passe",
|
||||
"moreInfo": "Qu'est-ce que Human Connection?",
|
||||
"moreInfoURL": "https://human-connection.org/fr/",
|
||||
"moreInfoHint": "à la page de présentation",
|
||||
"hello": "Bonjour"
|
||||
},
|
||||
"editor": {
|
||||
"placeholder": "Écrivez quelque chose d'inspirant..."
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mon profil",
|
||||
"memberSince": "Membre depuis",
|
||||
"follow": "Suivre",
|
||||
"followers": "Suiveurs",
|
||||
"following": "Suivant"
|
||||
"following": "Suivant",
|
||||
"shouted": "Recommandé",
|
||||
"commented": "Comentado",
|
||||
"userAnonym": "Anónimo",
|
||||
"socialMedia": "Où d'autre puis-je trouver"
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"mentioned": "a parlé de vous dans un article"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Rechercher",
|
||||
"hint": "Qu'est-ce que vous cherchez ?",
|
||||
"failed": "Rien trouvé"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Paramètres",
|
||||
"name": "Configurations",
|
||||
"data": {
|
||||
"name": "Vos données"
|
||||
"name": "Vos données",
|
||||
"labelName": "Votre nom",
|
||||
"namePlaceholder": "Fémanon Funny",
|
||||
"labelCity": "Votre ville ou région",
|
||||
"labelBio": "À propos de vous",
|
||||
"success": "Vos données ont été mises à jour avec succès !"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sécurité"
|
||||
"name": "Sécurité",
|
||||
"change-password": {
|
||||
"button": "Modifier le mot de passe",
|
||||
"success": "Mot de passe modifié avec succès !",
|
||||
"label-old-password": "Votre ancien mot de passe",
|
||||
"label-new-password": "Votre nouveau mot de passe",
|
||||
"label-new-password-confirm": "Confirmez votre nouveau mot de passe",
|
||||
"message-old-password-required": "Entrez votre ancien mot de passe",
|
||||
"message-new-password-required": "Entrez un nouveau mot de passe",
|
||||
"message-new-password-confirm-required": "Confirmez votre nouveau mot de passe",
|
||||
"message-new-password-missmatch": "Tapez à nouveau le même mot de passe",
|
||||
"passwordSecurity": "Sécurité par mot de passe",
|
||||
"passwordStrength0": "Mot de passe très peu sûr",
|
||||
"passwordStrength1": "Mot de passe non sécurisé",
|
||||
"passwordStrength2": "Mot de passe médiocre",
|
||||
"passwordStrength3": "Mot de passe fort",
|
||||
"passwordStrength4": "Mot de passe très fort"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invite"
|
||||
"name": "invitations"
|
||||
},
|
||||
"download": {
|
||||
"name": "Télécharger les données"
|
||||
@ -36,7 +80,14 @@
|
||||
"name": "Mes organisations"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Langues"
|
||||
"name": "langues"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Médias sociaux",
|
||||
"placeholder": "Ajouter une URL pour les médias sociaux",
|
||||
"submit": "Ajouter un lien",
|
||||
"successAdd": "Les médias sociaux ont été ajoutés. Profil mis à jour !",
|
||||
"successDelete": "Médias sociaux supprimé. Profil mis à jour !"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
@ -51,7 +102,7 @@
|
||||
"projects": "Projets",
|
||||
"invites": "Invite",
|
||||
"follows": "Suit",
|
||||
"shouts": "Cris"
|
||||
"shouts": "Recommandé"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisations"
|
||||
@ -95,13 +146,18 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"your": {
|
||||
"post": "Votre message ::: Votre messages",
|
||||
"comment": "Votre Commentaire ::: Votre Commentaires ",
|
||||
"shout": "Votre Recommandation ::: Votre Recommandations"
|
||||
},
|
||||
"post": "Message ::: Messages",
|
||||
"comment": "Commentaire ::: Commentaires",
|
||||
"letsTalk": "Parlons-en",
|
||||
"versus": "Versus",
|
||||
"moreInfo": "Plus d'infos",
|
||||
"takeAction": "Passer à l'action",
|
||||
"shout": "Shout ::: Shouts",
|
||||
"shout": "Recommandation ::: Recommandations",
|
||||
"user": "Utilisateur ::: Utilisateurs",
|
||||
"category": "Catégorie ::: Catégories",
|
||||
"organization": "Organisation ::: Organisations",
|
||||
@ -112,33 +168,64 @@
|
||||
"loading": "chargement",
|
||||
"reportContent": "Signaler"
|
||||
},
|
||||
"actions": {
|
||||
"loading": "chargement",
|
||||
"loadMore": "charger plus",
|
||||
"create": "Créer",
|
||||
"save": "sauvegarde",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Modération",
|
||||
"reports": {
|
||||
"empty": "Félicitations, rien à modérer.",
|
||||
"name": "Signalisations",
|
||||
"reporter": "signalé par"
|
||||
"name": "Rapports",
|
||||
"submitter": "signalé par",
|
||||
"disabledBy": "ddésactivé par"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"submit": "Désactiver",
|
||||
"cancel": "Annuler",
|
||||
"success": "Désactivé avec succès",
|
||||
"user": {
|
||||
"title": "Désactiver l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
"message": "Souhaitez-vous vraiment désactiver l'utilisateur \" <b> {name} </b> \"?"
|
||||
"message": "Voulez-vous vraiment désactiver l'utilisateur \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Désactiver l'apport",
|
||||
"type": "apport",
|
||||
"message": "Souhaitez-vous vraiment signaler l'entrée\" <b> {name} </b> \"?"
|
||||
"title": "Cotisation d'invalidité",
|
||||
"type": "Contribution",
|
||||
"message": "Voulez-vous vraiment désactiver la contribution \"<b> {name} </b> \"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Désactiver le commentaire",
|
||||
"title": "Désactiver commentaire",
|
||||
"type": "Commentaire",
|
||||
"message": "Souhaitez-vous vraiment désactiver le commentaire de \"<b>{name}</b>\" ?"
|
||||
"message": "Voulez-vous vraiment désactiver le commentaire de \"<b>{name}</b>\" ?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"submit": "Supprimer",
|
||||
"cancel": "Annuler",
|
||||
"contribution": {
|
||||
"title": "Supprimer la contribution",
|
||||
"type": "Contribution",
|
||||
"message": "Voulez-vous vraiment supprimer la contribution \"<b>{name}</b>\" löschen möchtest?",
|
||||
"success": "Contribution supprimée avec succès !"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Supprimer un commentaire",
|
||||
"type": "Commentaire",
|
||||
"message": "Voulez-vous vraiment supprimer le commentaire de \"<b>{name}</b>\" löschen möchtest?",
|
||||
"success": "Commentaire supprimé avec succès !"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"submit": "Envoyer le rapport",
|
||||
"submit": "Rapport",
|
||||
"cancel": "Annuler",
|
||||
"success": "Merci de nous avoir fait part de vos commentaires!",
|
||||
"user": {
|
||||
"title": "Signaler l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
@ -155,15 +242,36 @@
|
||||
"message": "Souhaitez-vous vraiment signaler l'utilisateur \" <b> {name} </b> \"?"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"cancel": "Annuler"
|
||||
"followButton": {
|
||||
"follow": "découler",
|
||||
"following": "Je suis les"
|
||||
},
|
||||
"contribution": {
|
||||
"edit": "Rédiger l'apport",
|
||||
"delete": "Supprimer l'entrée"
|
||||
"shoutButton": {
|
||||
"shouted": "recommandé"
|
||||
},
|
||||
"comment": {
|
||||
"edit": "Rédiger un commentaire",
|
||||
"delete": "Supprimer le commentaire"
|
||||
"release": {
|
||||
"submit": "Relâchez",
|
||||
"cancel": "Annuler",
|
||||
"success": "Relâchez avec succès!",
|
||||
"user": {
|
||||
"title": "Validation par l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
"message": "Voulez-vous vraiment libérer l'utilisateur \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Versement de la contribution",
|
||||
"type": "Contribution",
|
||||
"message": "Voulez-vous vraiment débloquer la contribution \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Publication des commentaires",
|
||||
"type": "Commentaire",
|
||||
"message": "Voulez-vous vraiment publier le commentaire de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Téléchargement réussi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
async deletePostCallback(postDisplayType = 'list') {
|
||||
// console.log('inside "deletePostCallback" !!! ', this.post)
|
||||
try {
|
||||
var gqlMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
DeletePost(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
await this.$apollo.mutate({
|
||||
mutation: gqlMutation,
|
||||
variables: {
|
||||
id: this.post.id,
|
||||
},
|
||||
})
|
||||
this.$toast.success(this.$t('delete.contribution.success'))
|
||||
// console.log('called "this.$t" !!!')
|
||||
switch (postDisplayType) {
|
||||
case 'list':
|
||||
this.$emit('deletePost')
|
||||
// console.log('emitted "deletePost" !!!')
|
||||
break
|
||||
default:
|
||||
// case 'page'
|
||||
// console.log('called "this.$router.history.push" !!!')
|
||||
this.$router.history.push('/') // Single page type: Redirect to index (main) page
|
||||
break
|
||||
}
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -56,10 +56,10 @@
|
||||
"@nuxtjs/style-resources": "~0.1.2",
|
||||
"accounting": "~0.4.1",
|
||||
"apollo-cache-inmemory": "~1.5.1",
|
||||
"apollo-client": "~2.6.2",
|
||||
"cookie-universal-nuxt": "~2.0.14",
|
||||
"apollo-client": "~2.6.3",
|
||||
"cookie-universal-nuxt": "~2.0.16",
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-alpha.31",
|
||||
"date-fns": "2.0.0-alpha.34",
|
||||
"express": "~4.17.1",
|
||||
"graphql": "~14.3.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
@ -69,8 +69,8 @@
|
||||
"nuxt-env": "~0.1.0",
|
||||
"stack-utils": "^1.0.2",
|
||||
"string-hash": "^1.1.3",
|
||||
"tiptap": "1.20.1",
|
||||
"tiptap-extensions": "1.21.0",
|
||||
"tiptap": "1.21.0",
|
||||
"tiptap-extensions": "1.22.2",
|
||||
"v-tooltip": "~2.0.2",
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-izitoast": "1.1.2",
|
||||
@ -87,10 +87,10 @@
|
||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||
"@vue/test-utils": "~1.0.0-beta.29",
|
||||
"babel-core": "~7.0.0-bridge.0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-eslint": "~10.0.2",
|
||||
"babel-jest": "~24.8.0",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~4.3.0",
|
||||
"eslint-config-prettier": "~5.0.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-loader": "~2.1.2",
|
||||
"eslint-plugin-import": "~2.17.3",
|
||||
@ -104,9 +104,9 @@
|
||||
"jest": "~24.8.0",
|
||||
"node-sass": "~4.12.0",
|
||||
"nodemon": "~1.19.1",
|
||||
"prettier": "~1.18.0",
|
||||
"prettier": "~1.18.2",
|
||||
"sass-loader": "~7.1.0",
|
||||
"tippy.js": "^4.3.3",
|
||||
"tippy.js": "^4.3.4",
|
||||
"vue-jest": "~3.0.4",
|
||||
"vue-svg-loader": "~0.12.0"
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
|
||||
@deletePost="deletePost(index, post.id)"
|
||||
@removePostFromList="deletePost(index, post.id)"
|
||||
/>
|
||||
</ds-flex>
|
||||
<no-ssr>
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@ -77,6 +77,17 @@ export default {
|
||||
]
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
if (to.hash === '#comments') {
|
||||
window.scroll({
|
||||
top: document.getElementById('comments').offsetTop,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ describe('PostSlug', () => {
|
||||
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
describe('test mixin "PostMutationHelpers"', () => {
|
||||
describe('test Post callbacks', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.setData({
|
||||
@ -70,22 +70,14 @@ describe('PostSlug', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('deletion of Post from Page by invoking "deletePostCallback(`page`)"', () => {
|
||||
describe('deletion of Post from Page by invoking "deletePostCallback()"', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.deletePostCallback('page')
|
||||
wrapper.vm.deletePostCallback()
|
||||
})
|
||||
|
||||
describe('after timeout', () => {
|
||||
beforeEach(jest.runAllTimers)
|
||||
|
||||
it('not emits "deletePost"', () => {
|
||||
expect(wrapper.emitted().deletePost).toBeFalsy()
|
||||
})
|
||||
|
||||
it('does go to index (main) page', () => {
|
||||
expect(mocks.$router.history.push).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does call mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@ -93,6 +85,10 @@ describe('PostSlug', () => {
|
||||
it('mutation is successful', () => {
|
||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does go to index (main) page', () => {
|
||||
expect(mocks.$router.history.push).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -7,19 +7,18 @@
|
||||
>
|
||||
<ds-space margin-bottom="small" />
|
||||
<hc-user :user="post.author" :date-time="post.createdAt" />
|
||||
<!-- Content Menu (can open Modals) -->
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
resource-type="contribution"
|
||||
:resource="post"
|
||||
:callbacks="{ confirm: () => deletePostCallback('page'), cancel: null }"
|
||||
:is-owner="isAuthor(post.author.id)"
|
||||
:modalsData="menuModalsData"
|
||||
:is-owner="isAuthor(post.author ? post.author.id : null)"
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space margin-bottom="small" />
|
||||
<ds-heading tag="h3" no-margin>
|
||||
{{ post.title }}
|
||||
</ds-heading>
|
||||
<ds-heading tag="h3" no-margin>{{ post.title }}</ds-heading>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Content -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
@ -72,7 +71,7 @@ import HcUser from '~/components/User'
|
||||
import HcShoutButton from '~/components/ShoutButton.vue'
|
||||
import HcCommentForm from '~/components/comments/CommentForm'
|
||||
import HcCommentList from '~/components/comments/CommentList'
|
||||
import PostMutationHelpers from '~/mixins/PostMutationHelpers'
|
||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||
|
||||
export default {
|
||||
name: 'PostSlug',
|
||||
@ -89,7 +88,6 @@ export default {
|
||||
HcCommentForm,
|
||||
HcCommentList,
|
||||
},
|
||||
mixins: [PostMutationHelpers],
|
||||
head() {
|
||||
return {
|
||||
title: this.title,
|
||||
@ -211,10 +209,28 @@ export default {
|
||||
this.ready = true
|
||||
}, 50)
|
||||
},
|
||||
computed: {
|
||||
menuModalsData() {
|
||||
return postMenuModalsData(
|
||||
// "this.post" may not always be defined at the beginning …
|
||||
this.post ? this.$filters.truncate(this.post.title, 30) : '',
|
||||
this.deletePostCallback,
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isAuthor(id) {
|
||||
return this.$store.getters['auth/user'].id === id
|
||||
},
|
||||
async deletePostCallback() {
|
||||
try {
|
||||
await this.$apollo.mutate(deletePostMutation(this.post.id))
|
||||
this.$toast.success(this.$t('delete.contribution.success'))
|
||||
this.$router.history.push('/') // Redirect to index (main) page
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<ds-card>
|
||||
<h2 style="margin-bottom: .2em;">
|
||||
Mehr Informationen
|
||||
</h2>
|
||||
<h2 style="margin-bottom: .2em;">Mehr Informationen</h2>
|
||||
<p>Hier findest du weitere infos zum Thema.</p>
|
||||
<ds-space />
|
||||
<h3>
|
||||
@ -42,7 +40,7 @@
|
||||
:key="relatedPost.id"
|
||||
:post="relatedPost"
|
||||
:width="{ base: '100%', lg: 1 }"
|
||||
@deletePost="post.relatedContributions.splice(index, 1)"
|
||||
@removePostFromList="post.relatedContributions.splice(index, 1)"
|
||||
/>
|
||||
</ds-flex>
|
||||
<hc-empty v-else margin="large" icon="file" message="No related Posts" />
|
||||
|
||||
@ -3,9 +3,7 @@
|
||||
<ds-flex-item :width="{ base: '100%', md: 3 }">
|
||||
<hc-contribution-form :contribution="contribution" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }">
|
||||
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }"> </ds-flex-item>
|
||||
</ds-flex>
|
||||
</template>
|
||||
|
||||
@ -49,6 +47,7 @@ export default {
|
||||
deleted
|
||||
slug
|
||||
image
|
||||
language
|
||||
author {
|
||||
id
|
||||
disabled
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import ProfileSlug from './_slug.vue'
|
||||
import Vuex from 'vuex'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
localVue.filter('date', d => d)
|
||||
|
||||
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
|
||||
describe('ProfileSlug', () => {
|
||||
let wrapper
|
||||
@ -20,7 +27,13 @@ describe('ProfileSlug', () => {
|
||||
name: 'It is a post',
|
||||
},
|
||||
$t: jest.fn(),
|
||||
// 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're mocking router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
||||
$route: {
|
||||
params: {
|
||||
id: '4711',
|
||||
slug: 'john-doe',
|
||||
},
|
||||
},
|
||||
$router: {
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
@ -31,48 +44,138 @@ describe('ProfileSlug', () => {
|
||||
error: jest.fn(),
|
||||
},
|
||||
$apollo: {
|
||||
loading: false,
|
||||
mutate: jest.fn().mockResolvedValue(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
describe('mount', () => {
|
||||
Wrapper = () => {
|
||||
return shallowMount(ProfileSlug, {
|
||||
return mount(ProfileSlug, {
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
describe('test mixin "PostMutationHelpers"', () => {
|
||||
describe('given an authenticated user', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
mocks.$filters = {
|
||||
removeLinks: c => c,
|
||||
truncate: a => a,
|
||||
}
|
||||
mocks.$store = {
|
||||
getters: {
|
||||
'auth/user': {
|
||||
id: 'u23',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('deletion of Post from List by invoking "deletePostCallback(`list`)"', () => {
|
||||
describe('given a user for the profile', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.deletePostCallback('list')
|
||||
wrapper = Wrapper()
|
||||
wrapper.setData({
|
||||
User: [
|
||||
{
|
||||
id: 'u3',
|
||||
name: 'Bob the builder',
|
||||
contributionsCount: 6,
|
||||
shoutedCount: 7,
|
||||
commentedCount: 8,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
describe('after timeout', () => {
|
||||
beforeEach(jest.runAllTimers)
|
||||
it('displays name of the user', () => {
|
||||
expect(wrapper.text()).toContain('Bob the builder')
|
||||
})
|
||||
|
||||
it('emits "deletePost"', () => {
|
||||
expect(wrapper.emitted().deletePost.length).toBe(1)
|
||||
describe('load more button', () => {
|
||||
const aPost = {
|
||||
title: 'I am a post',
|
||||
content: 'This is my content',
|
||||
contentExcerpt: 'This is my content',
|
||||
}
|
||||
|
||||
describe('currently no posts available (e.g. after tab switching)', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setData({
|
||||
Post: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('displays no "load more" button', () => {
|
||||
expect(wrapper.find('.load-more').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('apollo client in `loading` state', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.$apollo.loading = true
|
||||
})
|
||||
|
||||
it('never displays more than one loading spinner', () => {
|
||||
expect(wrapper.findAll('.ds-spinner')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('displays a loading spinner below the posts list', () => {
|
||||
expect(wrapper.find('.user-profile-posts-list .ds-spinner').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not go to index (main) page', () => {
|
||||
expect(mocks.$router.history.push).not.toHaveBeenCalled()
|
||||
describe('pagination returned less posts than available', () => {
|
||||
beforeEach(() => {
|
||||
const posts = [1, 2, 3, 4, 5].map(id => {
|
||||
return {
|
||||
...aPost,
|
||||
id,
|
||||
}
|
||||
})
|
||||
|
||||
wrapper.setData({
|
||||
Post: posts,
|
||||
})
|
||||
})
|
||||
|
||||
it('displays a "load more" button', () => {
|
||||
expect(wrapper.find('.load-more').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('apollo client in `loading` state', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.$apollo.loading = true
|
||||
})
|
||||
|
||||
it('never displays more than one loading spinner', () => {
|
||||
expect(wrapper.findAll('.ds-spinner')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('displays a loading spinner below the posts list', () => {
|
||||
expect(wrapper.find('.load-more .ds-spinner').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does call mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
describe('pagination returned as many posts as available', () => {
|
||||
beforeEach(() => {
|
||||
const posts = [1, 2, 3, 4, 5, 6].map(id => {
|
||||
return {
|
||||
...aPost,
|
||||
id,
|
||||
}
|
||||
})
|
||||
|
||||
it('mutation is successful', () => {
|
||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||
wrapper.setData({
|
||||
Post: posts,
|
||||
})
|
||||
})
|
||||
|
||||
it('displays no "load more" button', () => {
|
||||
expect(wrapper.find('.load-more').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,12 +14,12 @@
|
||||
<hc-avatar :user="user" class="profile-avatar" size="x-large"></hc-avatar>
|
||||
</hc-upload>
|
||||
<hc-avatar v-else :user="user" class="profile-avatar" size="x-large" />
|
||||
<!-- Menu -->
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
resource-type="user"
|
||||
:resource="user"
|
||||
:callbacks="{ confirm: deletePostCallback, cancel: null }"
|
||||
:is-owner="myProfile"
|
||||
class="user-content-menu"
|
||||
/>
|
||||
@ -144,69 +144,50 @@
|
||||
</ds-card>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
|
||||
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
|
||||
<ds-flex :width="{ base: '100%' }" gutter="small">
|
||||
<ds-flex class="user-profile-posts-list" :width="{ base: '100%' }" gutter="small">
|
||||
<ds-flex-item class="profile-top-navigation">
|
||||
<ds-card class="ds-tab-nav">
|
||||
<ds-flex>
|
||||
<ds-flex-item
|
||||
v-tooltip="{
|
||||
content: $t('common.your.post', null, user.contributionsCount),
|
||||
placement: 'right',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
class="ds-tab-nav-item pointer ds-tab-nav-item-active"
|
||||
@click="tabActivity('posts', $event)"
|
||||
>
|
||||
<ds-space margin="small">
|
||||
<!-- TODO: find better solution for rendering errors -->
|
||||
<no-ssr>
|
||||
<ds-number :label="$t('common.post', null, user.contributionsCount)">
|
||||
<hc-count-to slot="count" :end-val="user.contributionsCount" />
|
||||
</ds-number>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
v-tooltip="{
|
||||
content: $t('common.your.comment', null, user.commentsCount),
|
||||
placement: 'right',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
class="ds-tab-nav-item pointer"
|
||||
@click="tabActivity('commented', $event)"
|
||||
>
|
||||
<ds-space margin="small">
|
||||
<!-- TODO: find better solution for rendering errors -->
|
||||
|
||||
<no-ssr>
|
||||
<ds-number :label="$t('profile.commented')">
|
||||
<hc-count-to slot="count" :end-val="user.commentsCount" />
|
||||
</ds-number>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
|
||||
<ds-flex-item
|
||||
v-tooltip="{
|
||||
content: $t('common.your.shout', null, user.shoutedCount),
|
||||
placement: 'right',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
class="ds-tab-nav-item pointer"
|
||||
>
|
||||
<ds-space margin="small">
|
||||
<!-- TODO: find better solution for rendering errors -->
|
||||
<no-ssr>
|
||||
<ds-number :label="$t('profile.shouted')">
|
||||
<hc-count-to slot="count" :end-val="user.shoutedCount" />
|
||||
</ds-number>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<ul class="Tabs">
|
||||
<li class="Tabs__tab Tab pointer" :class="{ active: tabActive === 'post' }">
|
||||
<a @click="handleTab('post')">
|
||||
<ds-space margin="small">
|
||||
<no-ssr placeholder="Loading...">
|
||||
<ds-number :label="$t('common.post', null, user.contributionsCount)">
|
||||
<hc-count-to slot="count" :end-val="user.contributionsCount" />
|
||||
</ds-number>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
</a>
|
||||
</li>
|
||||
<li class="Tabs__tab Tab pointer" :class="{ active: tabActive === 'comment' }">
|
||||
<a @click="handleTab('comment')">
|
||||
<ds-space margin="small">
|
||||
<no-ssr placeholder="Loading...">
|
||||
<ds-number :label="$t('profile.commented')">
|
||||
<hc-count-to slot="count" :end-val="user.commentedCount" />
|
||||
</ds-number>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
</a>
|
||||
</li>
|
||||
<li class="Tabs__tab Tab pointer" :class="{ active: tabActive === 'shout' }">
|
||||
<a @click="handleTab('shout')">
|
||||
<ds-space margin="small">
|
||||
<no-ssr placeholder="Loading...">
|
||||
<ds-number :label="$t('profile.shouted')">
|
||||
<hc-count-to slot="count" :end-val="user.shoutedCount" />
|
||||
</ds-number>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
</a>
|
||||
</li>
|
||||
<li class="Tabs__presentation-slider" role="presentation"></li>
|
||||
</ul>
|
||||
</ds-card>
|
||||
</ds-flex-item>
|
||||
|
||||
<ds-flex-item style="text-align: center">
|
||||
<ds-button
|
||||
v-if="myProfile"
|
||||
@ -218,15 +199,23 @@
|
||||
primary
|
||||
/>
|
||||
</ds-flex-item>
|
||||
|
||||
<template v-if="activePosts.length">
|
||||
<hc-post-card
|
||||
v-for="(post, index) in activePosts"
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
:width="{ base: '100%', md: '100%', xl: '50%' }"
|
||||
@deletePost="user.contributions.splice(index, 1)"
|
||||
@removePostFromList="activePosts.splice(index, 1)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="$apollo.loading">
|
||||
<ds-flex-item>
|
||||
<ds-section centered>
|
||||
<ds-spinner size="base"></ds-spinner>
|
||||
</ds-section>
|
||||
</ds-flex-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ds-flex-item :width="{ base: '100%' }">
|
||||
<hc-empty margin="xx-large" icon="file" />
|
||||
@ -241,7 +230,6 @@
|
||||
|
||||
<script>
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
|
||||
import User from '~/components/User'
|
||||
import HcPostCard from '~/components/PostCard'
|
||||
import HcFollowButton from '~/components/FollowButton.vue'
|
||||
@ -252,9 +240,19 @@ import HcEmpty from '~/components/Empty.vue'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
import HcUpload from '~/components/Upload'
|
||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
||||
import PostMutationHelpers from '~/mixins/PostMutationHelpers'
|
||||
import PostQuery from '~/graphql/UserProfile/Post.js'
|
||||
import UserQuery from '~/graphql/UserProfile/User.js'
|
||||
|
||||
const tabToFilterMapping = ({ tab, id }) => {
|
||||
return {
|
||||
post: { author: { id } },
|
||||
comment: { comments_some: { author: { id } } },
|
||||
shout: { shoutedBy_some: { id } },
|
||||
}[tab]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'HcUserProfile',
|
||||
components: {
|
||||
User,
|
||||
HcPostCard,
|
||||
@ -267,22 +265,34 @@ export default {
|
||||
ContentMenu,
|
||||
HcUpload,
|
||||
},
|
||||
mixins: [PostMutationHelpers],
|
||||
transition: {
|
||||
name: 'slide-up',
|
||||
mode: 'out-in',
|
||||
},
|
||||
data() {
|
||||
const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
|
||||
return {
|
||||
User: [],
|
||||
Post: [],
|
||||
activePosts: [],
|
||||
voted: false,
|
||||
page: 1,
|
||||
pageSize: 6,
|
||||
tabActive: 'post',
|
||||
filter,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasMore() {
|
||||
const total = {
|
||||
post: this.user.contributionsCount,
|
||||
shout: this.user.shoutedCount,
|
||||
comment: this.user.commentedCount,
|
||||
}[this.tabActive]
|
||||
return this.Post && this.Post.length < total
|
||||
},
|
||||
myProfile() {
|
||||
return this.$route.params.slug === this.$store.getters['auth/user'].slug
|
||||
return this.$route.params.id === this.$store.getters['auth/user'].id
|
||||
},
|
||||
followedByCount() {
|
||||
let count = Number(this.user.followedByCount) || 0
|
||||
@ -294,17 +304,6 @@ export default {
|
||||
offset() {
|
||||
return (this.page - 1) * this.pageSize
|
||||
},
|
||||
hasMore() {
|
||||
return (
|
||||
this.user.contributions && this.user.contributions.length < this.user.contributionsCount
|
||||
)
|
||||
},
|
||||
activePosts() {
|
||||
if (!this.user.contributions) {
|
||||
return []
|
||||
}
|
||||
return this.uniq(this.user.contributions.filter(post => !post.deleted))
|
||||
},
|
||||
socialMediaLinks() {
|
||||
const { socialMedia = [] } = this.user
|
||||
return socialMedia.map(socialMedia => {
|
||||
@ -327,8 +326,16 @@ export default {
|
||||
throw new Error('User not found!')
|
||||
}
|
||||
},
|
||||
Post(val) {
|
||||
this.activePosts = this.setActivePosts()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleTab(tab) {
|
||||
this.tabActive = tab
|
||||
this.Post = null
|
||||
this.filter = tabToFilterMapping({ tab, id: this.$route.params.id })
|
||||
},
|
||||
uniq(items, field = 'id') {
|
||||
return uniqBy(items, field)
|
||||
},
|
||||
@ -340,39 +347,51 @@ export default {
|
||||
// this.page++
|
||||
// Fetch more data and transform the original result
|
||||
this.page++
|
||||
this.$apollo.queries.User.fetchMore({
|
||||
this.$apollo.queries.Post.fetchMore({
|
||||
variables: {
|
||||
slug: this.$route.params.slug,
|
||||
filter: this.filter,
|
||||
first: this.pageSize,
|
||||
offset: this.offset,
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
let output = { User: this.User }
|
||||
output.User[0].contributions = [
|
||||
...previousResult.User[0].contributions,
|
||||
...fetchMoreResult.User[0].contributions,
|
||||
]
|
||||
let output = { Post: this.Post }
|
||||
output.Post = [...previousResult.Post, ...fetchMoreResult.Post]
|
||||
return output
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
})
|
||||
},
|
||||
setActivePosts() {
|
||||
if (!this.Post) {
|
||||
return []
|
||||
}
|
||||
return this.uniq(this.Post.filter(post => !post.deleted))
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
User: {
|
||||
Post: {
|
||||
query() {
|
||||
return require('~/graphql/UserProfileQuery.js').default(this)
|
||||
return PostQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
slug: this.$route.params.slug,
|
||||
filter: this.filter,
|
||||
first: this.pageSize,
|
||||
offset: 0,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
User: {
|
||||
query() {
|
||||
return UserQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return { id: this.$route.params.id }
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -381,18 +400,54 @@ export default {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ds-tab-nav .ds-card-content .ds-tab-nav-item:hover {
|
||||
border-bottom: 3px solid #c9c6ce;
|
||||
.Tab {
|
||||
border-collapse: collapse;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.Tab:hover {
|
||||
border-bottom: 2px solid #c9c6ce;
|
||||
}
|
||||
.Tabs {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
&__tab {
|
||||
float: left;
|
||||
width: 33.333%;
|
||||
text-align: center;
|
||||
&:first-child.active ~ .Tabs__presentation-slider {
|
||||
left: 0;
|
||||
}
|
||||
&:nth-child(2).active ~ .Tabs__presentation-slider {
|
||||
left: 33.333%;
|
||||
}
|
||||
&:nth-child(3).active ~ .Tabs__presentation-slider {
|
||||
left: calc(33.333% * 2);
|
||||
}
|
||||
}
|
||||
&__presentation-slider {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 33.333%;
|
||||
height: 2px;
|
||||
background-color: #17b53f;
|
||||
transition: left 0.25s;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-avatar.ds-avatar {
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-top: -60px;
|
||||
border: #fff 5px solid;
|
||||
}
|
||||
|
||||
.page-name-profile-id-slug {
|
||||
.ds-flex-item:first-child .content-menu {
|
||||
position: absolute;
|
||||
@ -400,17 +455,14 @@ export default {
|
||||
right: $space-x-small;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-top-navigation {
|
||||
position: sticky;
|
||||
top: 53px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.ds-tab-nav {
|
||||
.ds-card-content {
|
||||
padding: 0 !important;
|
||||
|
||||
.ds-tab-nav-item {
|
||||
&.ds-tab-nav-item-active {
|
||||
border-bottom: 3px solid #17b53f;
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-heading tag="h1">
|
||||
{{ $t('settings.name') }}
|
||||
</ds-heading>
|
||||
<ds-heading tag="h1">{{ $t('settings.name') }}</ds-heading>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '100%', md: '200px' }">
|
||||
<ds-menu :routes="routes" :is-exact="() => true" />
|
||||
@ -33,6 +31,10 @@ export default {
|
||||
name: this.$t('settings.social-media.name'),
|
||||
path: `/settings/my-social-media`,
|
||||
},
|
||||
{
|
||||
name: this.$t('settings.deleteUserAccount.name'),
|
||||
path: `/settings/delete-account`,
|
||||
},
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('settings.invites.name'),
|
||||
@ -44,10 +46,6 @@ export default {
|
||||
path: `/settings/data-download`
|
||||
}, */
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('settings.delete.name'),
|
||||
path: `/settings/delete-account`
|
||||
}, */
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('settings.organizations.name'),
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
<template>
|
||||
<ds-card :header="$t('settings.delete.name')">
|
||||
<hc-empty icon="tasks" message="Coming Soon…" />
|
||||
</ds-card>
|
||||
<delete-data />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import DeleteData from '~/components/DeleteData/DeleteData.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEmpty,
|
||||
DeleteData,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -2,11 +2,13 @@ import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import MySocialMedia from './my-social-media.vue'
|
||||
import Vuex from 'vuex'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(Filters)
|
||||
|
||||
describe('my-social-media.vue', () => {
|
||||
let wrapper
|
||||
|
||||
@ -4,12 +4,7 @@
|
||||
<ds-list>
|
||||
<ds-list-item v-for="link in socialMediaLinks" :key="link.id">
|
||||
<a :href="link.url" target="_blank">
|
||||
<hc-image
|
||||
:image-props="{ src: link.favicon }"
|
||||
alt="Social Media link"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
<img :src="link.favicon | proxyApiUrl" alt="Social Media link" width="16" height="16" />
|
||||
{{ link.url }}
|
||||
</a>
|
||||
|
||||
@ -44,12 +39,8 @@
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import HcImage from '~/components/Image'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcImage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
|
||||
@ -93,6 +93,10 @@ export default ({ app = {} }) => {
|
||||
|
||||
return excerpt
|
||||
},
|
||||
proxyApiUrl: url => {
|
||||
if (!url) return url
|
||||
return url.startsWith('/') ? url.replace('/', '/api/') : url
|
||||
},
|
||||
})
|
||||
|
||||
// add all methods as filters on each vue component
|
||||
|
||||
@ -79,6 +79,8 @@ export const actions = {
|
||||
role
|
||||
about
|
||||
locationName
|
||||
contributionsCount
|
||||
commentsCount
|
||||
socialMedia {
|
||||
id
|
||||
url
|
||||
|
||||
17
webapp/store/editor.js
Normal file
17
webapp/store/editor.js
Normal file
@ -0,0 +1,17 @@
|
||||
export const state = () => {
|
||||
return {
|
||||
placeholder: null,
|
||||
}
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
placeholder(state) {
|
||||
return state.placeholder
|
||||
},
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
SET_PLACEHOLDER_TEXT(state, text) {
|
||||
state.placeholder = text
|
||||
},
|
||||
}
|
||||
20
webapp/store/editor.spec.js
Normal file
20
webapp/store/editor.spec.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { getters, mutations } from './editor.js'
|
||||
|
||||
let state
|
||||
|
||||
describe('getters', () => {
|
||||
describe('placeholder', () => {
|
||||
it('return the value in state', () => {
|
||||
state = { placeholder: null }
|
||||
expect(getters.placeholder(state)).toBe(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutations', () => {
|
||||
it('SET_PLACEHOLDER_TEXT', () => {
|
||||
state = { placeholder: null }
|
||||
mutations.SET_PLACEHOLDER_TEXT(state, 'new placeholder')
|
||||
expect(getters.placeholder(state)).toBe('new placeholder')
|
||||
})
|
||||
})
|
||||
121
webapp/yarn.lock
121
webapp/yarn.lock
@ -1893,10 +1893,10 @@ apollo-cache@1.3.2, apollo-cache@^1.2.1:
|
||||
apollo-utilities "^1.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-client@^2.5.1, apollo-client@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.2.tgz#03b6af651e09b6e413e486ddc87464c85bd6e514"
|
||||
integrity sha512-oks1MaT5x7gHcPeC8vPC1UzzsKaEIC0tye+jg72eMDt5OKc7BobStTeS/o2Ib3e0ii40nKxGBnMdl/Xa/p56Yg==
|
||||
apollo-client@^2.5.1, 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==
|
||||
dependencies:
|
||||
"@types/zen-observable" "^0.8.0"
|
||||
apollo-cache "1.3.2"
|
||||
@ -2310,10 +2310,10 @@ babel-core@~7.0.0-bridge.0:
|
||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
||||
|
||||
babel-eslint@~10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
|
||||
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
|
||||
babel-eslint@~10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||
integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/parser" "^7.0.0"
|
||||
@ -3305,18 +3305,18 @@ cookie-signature@1.0.6:
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie-universal-nuxt@~2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.yarnpkg.com/cookie-universal-nuxt/-/cookie-universal-nuxt-2.0.14.tgz#6fdf8e928eadd7611c04a57614fe2e29b60eb971"
|
||||
integrity sha512-ih9Z0Z2K6eLaugTttGCVN85nogKseIFF/dqup3klvYC4mQS3+1IloqBqzTL/N7degBBAols2oppwYNDmaRtVig==
|
||||
cookie-universal-nuxt@~2.0.16:
|
||||
version "2.0.16"
|
||||
resolved "https://registry.yarnpkg.com/cookie-universal-nuxt/-/cookie-universal-nuxt-2.0.16.tgz#8d528098c973162b352199240e40da0e5429b13f"
|
||||
integrity sha512-wRK2zw8w+a5xPehb5kLbgOic/4mbjl2exUCxWZwGuttcwsFgOymiwDrCOzmQslqrDevPDL2SsBbH6wtOm7dB9g==
|
||||
dependencies:
|
||||
"@types/cookie" "^0.3.1"
|
||||
cookie-universal "^2.0.14"
|
||||
cookie-universal "^2.0.16"
|
||||
|
||||
cookie-universal@^2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.yarnpkg.com/cookie-universal/-/cookie-universal-2.0.14.tgz#1b4f27cffccfc2e47703fa235c1f67f931213041"
|
||||
integrity sha512-m6J0DQa4/RQvXhzUG37EY1ynK3Uq1BKzp5hotST9olrzjrRx+B0vNPx7azg0/X0XrYQvL7MMbPXwou8m0BNDwg==
|
||||
cookie-universal@^2.0.16:
|
||||
version "2.0.16"
|
||||
resolved "https://registry.yarnpkg.com/cookie-universal/-/cookie-universal-2.0.16.tgz#ec8b55789b502a377ef02ad230923c1dfa5c1061"
|
||||
integrity sha512-EHtQ5Tg3UoUHG7LmeV3rlV3iYthkhUuYZ0y86EseypxGcUuvzxuHExEb6mHKDhDPrIrdewAHdG/aCHuG/T4zEg==
|
||||
dependencies:
|
||||
"@types/cookie" "^0.3.1"
|
||||
cookie "^0.3.1"
|
||||
@ -3754,10 +3754,10 @@ data-urls@^1.0.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@2.0.0-alpha.31:
|
||||
version "2.0.0-alpha.31"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.31.tgz#51bcfdca25dfc9bea334a556ab33dfc0bb00421c"
|
||||
integrity sha512-S19PwMqnbYsqcbCg02Yj9gv4veVNZ0OX7v2+zcd+Mq0RI7LoDKJipJjnMrTZ3Cc6blDuTce5G/pHXcVIGRwJWQ==
|
||||
date-fns@2.0.0-alpha.34:
|
||||
version "2.0.0-alpha.34"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.34.tgz#5d3ae7ca0d08915ccfc87a20545250af4e9c3cae"
|
||||
integrity sha512-yjSYUHASHvzOZl++cEms+Tw7oQOFA+7Z6/lL7L3lRO9j6pMfT48N6oEyvCGo/MVlH08XWmydgf8X9Y1eedf9sQ==
|
||||
|
||||
date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
@ -4236,10 +4236,10 @@ eslint-config-prettier@^3.3.0:
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
eslint-config-prettier@~4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0"
|
||||
integrity sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==
|
||||
eslint-config-prettier@~5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c"
|
||||
integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
@ -8859,10 +8859,10 @@ prettier@1.16.3:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
|
||||
integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
|
||||
|
||||
prettier@^1.15.2, prettier@~1.18.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.0.tgz#d1701ca9b2941864b52f3262b35946d2c9cd88f0"
|
||||
integrity sha512-YsdAD29M0+WY2xXZk3i0PA16olY9qZss+AuODxglXcJ+2ZBwFv+6k5tE8GS8/HKAthaajlS/WqhdgcjumOrPlg==
|
||||
prettier@^1.15.2, prettier@~1.18.2:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||
|
||||
pretty-bytes@^5.2.0:
|
||||
version "5.2.0"
|
||||
@ -9017,7 +9017,7 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.2.3:
|
||||
prosemirror-model "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-tables@^0.8.0, prosemirror-tables@^0.8.1:
|
||||
prosemirror-tables@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.8.1.tgz#ea99ad4effec99dd4e2fdb0b33cce4d2547eed83"
|
||||
integrity sha512-6eY8I+NkyrXAQ1gmYkKo7XDLZaj0iGutdc/zT0+VMY15IzgBINwcRP62+miaCTuneLTKufMYzfUB37NjGJaetw==
|
||||
@ -9035,17 +9035,12 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor
|
||||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
|
||||
prosemirror-utils@^0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.8.2.tgz#e0e4a47cd45b1cff3d84464446d9f92adf4a558b"
|
||||
integrity sha512-jNIj3/eREx4x2FU6pFEUDmdVmtoRBuLA6JTjgNum/84Nf+Ns+Y9l0Q//R8EL/Qm/5J5xTg5/s+hmQkXaHY+ilA==
|
||||
|
||||
prosemirror-utils@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.9.0.tgz#3ab616c94ccd61fcb18968f0d5aa273a9f1f28e4"
|
||||
integrity sha512-YcvmHcq7phbn+OagJSvmne92qZG9dOVfb3zfuA1HuyWUif3hUDt2Yfu299BHqVkEkUCF6FN7Gi9folDQntMhxA==
|
||||
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.9.6, prosemirror-view@^1.9.8:
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.9.8:
|
||||
version "1.9.8"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.9.8.tgz#47b961204a0b2e8ff87370c270d4f82598e81273"
|
||||
integrity sha512-yS4yrqxydvi7ddz9VFLeJgbfVd5g3/bMcRxb1PbWtG0i9OrPSsiHaEBJHLVeTbraGqRlAu+tbNLakO7RhUhp1w==
|
||||
@ -10558,14 +10553,14 @@ timsort@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
||||
|
||||
tippy.js@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.3.tgz#396304bea577bbff03f2700a1761329e8c1fce86"
|
||||
integrity sha512-2fPMlquzVQxpLoOd0eJA1sPZ86/R6zD/9985wV0d2zhhX52DiO3aeg7TTS/mBrUjgFwVZh19YLb4l2c8bJkQPw==
|
||||
tippy.js@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.4.tgz#9a91fd5ce8c401f181b7adaa6b2c27f3d105f3ba"
|
||||
integrity sha512-O2ukxHOJTLVYZ/TfHjNd8WgAWoefX9uk5QiWRdHfX2PR2lBpUU4BJQLl7U2Ykc8K7o16gTeHEElpuRfgD5b0aA==
|
||||
dependencies:
|
||||
popper.js "^1.14.7"
|
||||
|
||||
tiptap-commands@^1.10.5, tiptap-commands@^1.10.7:
|
||||
tiptap-commands@^1.10.6, tiptap-commands@^1.10.7:
|
||||
version "1.10.7"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.10.7.tgz#c177f875e8a90e05171fdd441190b4b7a769bd27"
|
||||
integrity sha512-Ij62dHkYvOFUcaj+xlbahhoqcY8bFIEho6fXKrxcjjtGzS2DyJnvXmWN6Ow65uZWXkf5Zf8ae6XnYNaKqP3Pyg==
|
||||
@ -10579,10 +10574,10 @@ tiptap-commands@^1.10.5, tiptap-commands@^1.10.7:
|
||||
prosemirror-utils "^0.9.0"
|
||||
tiptap-utils "^1.5.5"
|
||||
|
||||
tiptap-extensions@1.21.0:
|
||||
version "1.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.21.0.tgz#c2f228144b6943755d2de4617e11febe08c78a42"
|
||||
integrity sha512-dIm9Q/G1qL1+sEqQiPXElUzSBcRhXXPuyOdtHgMrncUaCbnaDxsHdkJl700OXfA/GCu7AlhUpd3R67Rmb+voCQ==
|
||||
tiptap-extensions@1.22.2:
|
||||
version "1.22.2"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.22.2.tgz#66b24ef63653481da16419efdf6459c7a9f518f7"
|
||||
integrity sha512-JJe2yAnbaKBWO/16+lfFphePphG9UYhp3wAUkzhSefJx3dtIdDfCmp5jBzZ6NLahUHNT/Z9WeYdhQmngAeVhoA==
|
||||
dependencies:
|
||||
lowlight "^1.12.1"
|
||||
prosemirror-collab "^1.1.2"
|
||||
@ -10593,20 +10588,10 @@ tiptap-extensions@1.21.0:
|
||||
prosemirror-transform "^1.1.3"
|
||||
prosemirror-utils "^0.9.0"
|
||||
prosemirror-view "^1.9.8"
|
||||
tiptap "^1.21.1"
|
||||
tiptap "^1.22.2"
|
||||
tiptap-commands "^1.10.7"
|
||||
|
||||
tiptap-utils@^1.5.3:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.5.3.tgz#7d4518a84eb6bfb10c98b19c901ca59123865f61"
|
||||
integrity sha512-025uF1acJ7jLuA9dnKIxp8TQelyQ0uJqtT1jlziT8UslwZT5pKzwDsNkadFbnEBp6O9wfLZH0TuUtAUDmuq0VA==
|
||||
dependencies:
|
||||
prosemirror-model "^1.7.0"
|
||||
prosemirror-state "^1.2.3"
|
||||
prosemirror-tables "^0.8.0"
|
||||
prosemirror-utils "^0.8.2"
|
||||
|
||||
tiptap-utils@^1.5.5:
|
||||
tiptap-utils@^1.5.4, tiptap-utils@^1.5.5:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.5.5.tgz#f06c70f0319d9215433b3466b6ece369a078de0f"
|
||||
integrity sha512-lnaCmIIkyK050qfsPeMDjoTW0sTqBHxfw1h2GXaX5Nr6JF4VQe0izgf2MI6LCd3PKecJBoW4ce5lNkDpV99+WA==
|
||||
@ -10616,10 +10601,10 @@ tiptap-utils@^1.5.5:
|
||||
prosemirror-tables "^0.8.1"
|
||||
prosemirror-utils "^0.9.0"
|
||||
|
||||
tiptap@1.20.1:
|
||||
version "1.20.1"
|
||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.20.1.tgz#d10fd0cd73a96bbb1f2d581da02ceda38fa8695b"
|
||||
integrity sha512-uVGxPknq+cQH0G8yyCHvo8p3jPMLZMnkLeFjcrTyiY9PXl6XsSJwOjtIg4GXnIyCcfz2jWI5mhJGzCD26cdJGA==
|
||||
tiptap@1.21.0:
|
||||
version "1.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.21.0.tgz#4d8c1365c611e41c8d4f3d7aa195ddaf891e605b"
|
||||
integrity sha512-MoOj/8OPMlmoAotIZjAIlUZ59yMMR83xReOw2rGjqbFOooncoY1rLEBp0xz5oe5FLYqoe8dKb+kzOoFERqckVQ==
|
||||
dependencies:
|
||||
prosemirror-commands "^1.0.8"
|
||||
prosemirror-dropcursor "^1.1.1"
|
||||
@ -10628,14 +10613,14 @@ tiptap@1.20.1:
|
||||
prosemirror-keymap "^1.0.1"
|
||||
prosemirror-model "^1.7.0"
|
||||
prosemirror-state "^1.2.3"
|
||||
prosemirror-view "^1.9.6"
|
||||
tiptap-commands "^1.10.5"
|
||||
tiptap-utils "^1.5.3"
|
||||
prosemirror-view "^1.9.8"
|
||||
tiptap-commands "^1.10.6"
|
||||
tiptap-utils "^1.5.4"
|
||||
|
||||
tiptap@^1.21.1:
|
||||
version "1.21.1"
|
||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.21.1.tgz#c0340375795088b899541b64ce86ae45e98d0369"
|
||||
integrity sha512-vCKT/UGorAx1SSX5+9vmtZa+WC+LKfJIArgkEJFXmxfZeyBhNXRSwR+rR+UtdPYi8V4CtmIwtv6eRPP+bH6SWA==
|
||||
tiptap@^1.22.2:
|
||||
version "1.22.2"
|
||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.22.2.tgz#f3f2b822d9ed087a853520c86593b2e6bd822bdd"
|
||||
integrity sha512-qMFQJ358Ga8gXzUAMaHGrZDab/IqZf28N8BYYo4hyFIMoEYQVWmBGWuSBHOgpxiueXpC9zMuCoIgrNWN3VpoNQ==
|
||||
dependencies:
|
||||
prosemirror-commands "^1.0.8"
|
||||
prosemirror-dropcursor "^1.1.1"
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@ -1805,10 +1805,10 @@ cucumber@^4.2.1:
|
||||
util-arity "^1.0.2"
|
||||
verror "^1.9.0"
|
||||
|
||||
cypress-cucumber-preprocessor@^1.11.2:
|
||||
version "1.11.2"
|
||||
resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.11.2.tgz#daa86805e25a39cea1cf2278f3b7cee204478853"
|
||||
integrity sha512-Cret/EmqGdC6QLUQrszDdzDt+y4aL0ViaOWfZ1PgM4GpAay4gHQ+j0mtTIBvRg8Y86w6NOfzaflcHKGk54v2XQ==
|
||||
cypress-cucumber-preprocessor@^1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.12.0.tgz#092428ba267331e3d2cc6e1309c331d17632b8b1"
|
||||
integrity sha512-uKrWbs51hGeHiLgcSZcjFvvVEW9UdStsLVpD1snuPuik9WE61kbZv7xumlPjRmkMF81zTUGnNLwZuAk3CV9dEw==
|
||||
dependencies:
|
||||
"@cypress/browserify-preprocessor" "^1.1.2"
|
||||
chai "^4.1.2"
|
||||
@ -1822,10 +1822,10 @@ cypress-cucumber-preprocessor@^1.11.2:
|
||||
glob "^7.1.2"
|
||||
through "^2.3.8"
|
||||
|
||||
cypress-file-upload@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.1.2.tgz#4a0024f99ca157565bf2b20c110e6e6874da28cb"
|
||||
integrity sha512-gZE2G7ZTD2Y8APrcgs+ATRMKs/IgH2rafCmi+8o99q5sDoNRLR+XKxOcoyWLehj9raGnO98YDYO8DY7k1VMGBw==
|
||||
cypress-file-upload@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.1.4.tgz#cc208cb937a3abb136b52309eaf4637d5676c5bd"
|
||||
integrity sha512-4aZeJOYFhYiP+nk9Mo5YHWqComsT24J9OBQVJzvkEzw7g1v2ogGe7nLT/U7Fsm/Xjl1Tyxsc0xxECa254WfQqg==
|
||||
|
||||
cypress-plugin-retries@^1.2.2:
|
||||
version "1.2.2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user