mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into post-needs-to-have-category#1222
This commit is contained in:
commit
a000719663
@ -90,7 +90,7 @@
|
|||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"neo4j-driver": "~1.7.5",
|
"neo4j-driver": "~1.7.5",
|
||||||
"neo4j-graphql-js": "^2.7.1",
|
"neo4j-graphql-js": "^2.7.1",
|
||||||
"neode": "^0.3.1",
|
"neode": "^0.3.2",
|
||||||
"node-fetch": "~2.6.0",
|
"node-fetch": "~2.6.0",
|
||||||
"nodemailer": "^6.3.0",
|
"nodemailer": "^6.3.0",
|
||||||
"npm-run-all": "~4.1.5",
|
"npm-run-all": "~4.1.5",
|
||||||
@ -98,7 +98,7 @@
|
|||||||
"sanitize-html": "~1.20.1",
|
"sanitize-html": "~1.20.1",
|
||||||
"slug": "~1.1.0",
|
"slug": "~1.1.0",
|
||||||
"trunc-html": "~1.1.2",
|
"trunc-html": "~1.1.2",
|
||||||
"uuid": "~3.3.2",
|
"uuid": "~3.3.3",
|
||||||
"wait-on": "~3.3.0"
|
"wait-on": "~3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -115,14 +115,14 @@
|
|||||||
"chai": "~4.2.0",
|
"chai": "~4.2.0",
|
||||||
"cucumber": "~5.1.0",
|
"cucumber": "~5.1.0",
|
||||||
"eslint": "~6.2.0",
|
"eslint": "~6.2.0",
|
||||||
"eslint-config-prettier": "~6.0.0",
|
"eslint-config-prettier": "~6.1.0",
|
||||||
"eslint-config-standard": "~13.0.1",
|
"eslint-config-standard": "~13.0.1",
|
||||||
"eslint-plugin-import": "~2.18.2",
|
"eslint-plugin-import": "~2.18.2",
|
||||||
"eslint-plugin-jest": "~22.15.1",
|
"eslint-plugin-jest": "~22.15.1",
|
||||||
"eslint-plugin-node": "~9.1.0",
|
"eslint-plugin-node": "~9.1.0",
|
||||||
"eslint-plugin-prettier": "~3.1.0",
|
"eslint-plugin-prettier": "~3.1.0",
|
||||||
"eslint-plugin-promise": "~4.2.1",
|
"eslint-plugin-promise": "~4.2.1",
|
||||||
"eslint-plugin-standard": "~4.0.0",
|
"eslint-plugin-standard": "~4.0.1",
|
||||||
"graphql-request": "~1.8.2",
|
"graphql-request": "~1.8.2",
|
||||||
"jest": "~24.9.0",
|
"jest": "~24.9.0",
|
||||||
"nodemon": "~1.19.1",
|
"nodemon": "~1.19.1",
|
||||||
|
|||||||
@ -1,39 +1,57 @@
|
|||||||
import extractMentionedUsers from './notifications/extractMentionedUsers'
|
import extractMentionedUsers from './notifications/extractMentionedUsers'
|
||||||
import extractHashtags from './hashtags/extractHashtags'
|
import extractHashtags from './hashtags/extractHashtags'
|
||||||
|
|
||||||
const notify = async (postId, idsOfMentionedUsers, context) => {
|
const notifyMentions = async (label, id, idsOfMentionedUsers, context) => {
|
||||||
|
if (!idsOfMentionedUsers.length) return
|
||||||
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const createdAt = new Date().toISOString()
|
const createdAt = new Date().toISOString()
|
||||||
const cypher = `
|
let cypher
|
||||||
MATCH(p:Post {id: $postId})<-[:WROTE]-(author:User)
|
if (label === 'Post') {
|
||||||
MATCH(u:User)
|
cypher = `
|
||||||
WHERE u.id in $idsOfMentionedUsers
|
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
||||||
AND NOT (u)<-[:BLOCKED]-(author)
|
MATCH (user: User)
|
||||||
CREATE(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt})
|
WHERE user.id in $idsOfMentionedUsers
|
||||||
MERGE (p)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u)
|
AND NOT (user)<-[:BLOCKED]-(author)
|
||||||
|
CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt })
|
||||||
|
MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
|
||||||
`
|
`
|
||||||
|
} else {
|
||||||
|
cypher = `
|
||||||
|
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||||
|
MATCH (user: User)
|
||||||
|
WHERE user.id in $idsOfMentionedUsers
|
||||||
|
AND NOT (user)<-[:BLOCKED]-(author)
|
||||||
|
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
||||||
|
CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt })
|
||||||
|
MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
|
||||||
|
`
|
||||||
|
}
|
||||||
await session.run(cypher, {
|
await session.run(cypher, {
|
||||||
idsOfMentionedUsers,
|
idsOfMentionedUsers,
|
||||||
|
label,
|
||||||
createdAt,
|
createdAt,
|
||||||
postId,
|
id,
|
||||||
})
|
})
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
||||||
|
if (!hashtags.length) return
|
||||||
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
// We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
|
// We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement
|
||||||
// functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
|
// functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted
|
||||||
// and no new Hashtags and relations will be created.
|
// and no new Hashtags and relations will be created.
|
||||||
const cypherDeletePreviousRelations = `
|
const cypherDeletePreviousRelations = `
|
||||||
MATCH (p:Post { id: $postId })-[previousRelations:TAGGED]->(t:Tag)
|
MATCH (p: Post { id: $postId })-[previousRelations: TAGGED]->(t: Tag)
|
||||||
DELETE previousRelations
|
DELETE previousRelations
|
||||||
RETURN p, t
|
RETURN p, t
|
||||||
`
|
`
|
||||||
const cypherCreateNewTagsAndRelations = `
|
const cypherCreateNewTagsAndRelations = `
|
||||||
MATCH (p:Post { id: $postId})
|
MATCH (p: Post { id: $postId})
|
||||||
UNWIND $hashtags AS tagName
|
UNWIND $hashtags AS tagName
|
||||||
MERGE (t:Tag { id: tagName, name: tagName, disabled: false, deleted: false })
|
MERGE (t: Tag { id: tagName, name: tagName, disabled: false, deleted: false })
|
||||||
MERGE (p)-[:TAGGED]->(t)
|
MERGE (p)-[:TAGGED]->(t)
|
||||||
RETURN p, t
|
RETURN p, t
|
||||||
`
|
`
|
||||||
@ -47,24 +65,32 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => {
|
|||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContentData = async (resolve, root, args, context, resolveInfo) => {
|
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
|
||||||
// extract user ids before xss-middleware removes classes via the following "resolve" call
|
|
||||||
const idsOfMentionedUsers = extractMentionedUsers(args.content)
|
const idsOfMentionedUsers = extractMentionedUsers(args.content)
|
||||||
// extract tag (hashtag) ids before xss-middleware removes classes via the following "resolve" call
|
|
||||||
const hashtags = extractHashtags(args.content)
|
const hashtags = extractHashtags(args.content)
|
||||||
|
|
||||||
// removes classes from the content
|
|
||||||
const post = await resolve(root, args, context, resolveInfo)
|
const post = await resolve(root, args, context, resolveInfo)
|
||||||
|
|
||||||
await notify(post.id, idsOfMentionedUsers, context)
|
await notifyMentions('Post', post.id, idsOfMentionedUsers, context)
|
||||||
await updateHashtagsOfPost(post.id, hashtags, context)
|
await updateHashtagsOfPost(post.id, hashtags, context)
|
||||||
|
|
||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
|
||||||
|
const idsOfMentionedUsers = extractMentionedUsers(args.content)
|
||||||
|
const comment = await resolve(root, args, context, resolveInfo)
|
||||||
|
|
||||||
|
await notifyMentions('Comment', comment.id, idsOfMentionedUsers, context)
|
||||||
|
|
||||||
|
return comment
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreatePost: handleContentData,
|
CreatePost: handleContentDataOfPost,
|
||||||
UpdatePost: handleContentData,
|
UpdatePost: handleContentDataOfPost,
|
||||||
|
CreateComment: handleContentDataOfComment,
|
||||||
|
UpdateComment: handleContentDataOfComment,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,6 +75,9 @@ describe('notifications', () => {
|
|||||||
post {
|
post {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
comment {
|
||||||
|
content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,12 +89,12 @@ describe('notifications', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('given another user', () => {
|
describe('given another user', () => {
|
||||||
let author
|
let postAuthor
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
author = await instance.create('User', {
|
postAuthor = await instance.create('User', {
|
||||||
email: 'author@example.org',
|
email: 'post-author@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
id: 'author',
|
id: 'postAuthor',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -101,7 +104,7 @@ describe('notifications', () => {
|
|||||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
|
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
|
||||||
|
|
||||||
const createPostAction = async () => {
|
const createPostAction = async () => {
|
||||||
authenticatedUser = await author.toJson()
|
authenticatedUser = await postAuthor.toJson()
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: createPostMutation,
|
mutation: createPostMutation,
|
||||||
variables: { id: 'p47', title, content, categoryIds },
|
variables: { id: 'p47', title, content, categoryIds },
|
||||||
@ -115,12 +118,27 @@ describe('notifications', () => {
|
|||||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: {
|
data: {
|
||||||
currentUser: { notifications: [{ read: false, post: { content: expectedContent } }] },
|
currentUser: {
|
||||||
|
notifications: [
|
||||||
|
{
|
||||||
|
read: false,
|
||||||
|
post: {
|
||||||
|
content: expectedContent,
|
||||||
|
},
|
||||||
|
comment: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
await expect(
|
await expect(
|
||||||
query({ query: notificationQuery, variables: { read: false } }),
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
).resolves.toEqual(expected)
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -140,7 +158,7 @@ describe('notifications', () => {
|
|||||||
@al-capone
|
@al-capone
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
authenticatedUser = await author.toJson()
|
authenticatedUser = await postAuthor.toJson()
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: updatePostMutation,
|
mutation: updatePostMutation,
|
||||||
variables: {
|
variables: {
|
||||||
@ -162,31 +180,114 @@ describe('notifications', () => {
|
|||||||
data: {
|
data: {
|
||||||
currentUser: {
|
currentUser: {
|
||||||
notifications: [
|
notifications: [
|
||||||
{ read: false, post: { content: expectedContent } },
|
{
|
||||||
{ read: false, post: { content: expectedContent } },
|
read: false,
|
||||||
|
post: {
|
||||||
|
content: expectedContent,
|
||||||
|
},
|
||||||
|
comment: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
read: false,
|
||||||
|
post: {
|
||||||
|
content: expectedContent,
|
||||||
|
},
|
||||||
|
comment: null,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await expect(
|
await expect(
|
||||||
query({ query: notificationQuery, variables: { read: false } }),
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
).resolves.toEqual(expected)
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('but the author of the post blocked me', () => {
|
describe('but the author of the post blocked me', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await author.relateTo(user, 'blocked')
|
await postAuthor.relateTo(user, 'blocked')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sends no notification', async () => {
|
it('sends no notification', async () => {
|
||||||
await createPostAction()
|
await createPostAction()
|
||||||
const expected = expect.objectContaining({
|
const expected = expect.objectContaining({
|
||||||
data: { currentUser: { notifications: [] } },
|
data: {
|
||||||
|
currentUser: {
|
||||||
|
notifications: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
await expect(
|
await expect(
|
||||||
query({ query: notificationQuery, variables: { read: false } }),
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('but the author of the post blocked me and a mentioner mentions me in a comment', () => {
|
||||||
|
const createCommentOnPostAction = async () => {
|
||||||
|
await createPostAction()
|
||||||
|
const createCommentMutation = gql`
|
||||||
|
mutation($id: ID, $postId: ID!, $commentContent: String!) {
|
||||||
|
CreateComment(id: $id, postId: $postId, content: $commentContent) {
|
||||||
|
id
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
authenticatedUser = await commentMentioner.toJson()
|
||||||
|
await mutate({
|
||||||
|
mutation: createCommentMutation,
|
||||||
|
variables: {
|
||||||
|
id: 'c47',
|
||||||
|
postId: 'p47',
|
||||||
|
commentContent:
|
||||||
|
'One mention of me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
authenticatedUser = await user.toJson()
|
||||||
|
}
|
||||||
|
let commentMentioner
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await postAuthor.relateTo(user, 'blocked')
|
||||||
|
commentMentioner = await instance.create('User', {
|
||||||
|
id: 'mentioner',
|
||||||
|
name: 'Mr Mentioner',
|
||||||
|
slug: 'mr-mentioner',
|
||||||
|
email: 'mentioner@example.org',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends no notification', async () => {
|
||||||
|
await createCommentOnPostAction()
|
||||||
|
const expected = expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
currentUser: {
|
||||||
|
notifications: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const { query } = createTestClient(server)
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
).resolves.toEqual(expected)
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -234,7 +335,10 @@ describe('Hashtags', () => {
|
|||||||
it('both Hashtags are created with the "id" set to their "name"', async () => {
|
it('both Hashtags are created with the "id" set to their "name"', async () => {
|
||||||
const expected = [{ id: 'Democracy' }, { id: 'Liberty' }]
|
const expected = [{ id: 'Democracy' }, { id: 'Liberty' }]
|
||||||
await expect(
|
await expect(
|
||||||
query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
|
query({
|
||||||
|
query: postWithHastagsQuery,
|
||||||
|
variables: postWithHastagsVariables,
|
||||||
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: {
|
data: {
|
||||||
@ -266,11 +370,18 @@ describe('Hashtags', () => {
|
|||||||
|
|
||||||
const expected = [{ id: 'Elections' }, { id: 'Liberty' }]
|
const expected = [{ id: 'Elections' }, { id: 'Liberty' }]
|
||||||
await expect(
|
await expect(
|
||||||
query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
|
query({
|
||||||
|
query: postWithHastagsQuery,
|
||||||
|
variables: postWithHastagsVariables,
|
||||||
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: {
|
data: {
|
||||||
Post: [{ tags: expect.arrayContaining(expected) }],
|
Post: [
|
||||||
|
{
|
||||||
|
tags: expect.arrayContaining(expected),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -27,7 +27,7 @@ afterEach(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Notification', () => {
|
describe('Notification', () => {
|
||||||
const query = gql`
|
const notificationQuery = gql`
|
||||||
query {
|
query {
|
||||||
Notification {
|
Notification {
|
||||||
id
|
id
|
||||||
@ -38,19 +38,24 @@ describe('Notification', () => {
|
|||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
client = new GraphQLClient(host)
|
client = new GraphQLClient(host)
|
||||||
await expect(client.request(query)).rejects.toThrow('Not Authorised')
|
await expect(client.request(notificationQuery)).rejects.toThrow('Not Authorised')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('currentUser { notifications }', () => {
|
describe('currentUser notifications', () => {
|
||||||
const variables = {}
|
const variables = {}
|
||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
let headers
|
let headers
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
headers = await login({
|
||||||
client = new GraphQLClient(host, { headers })
|
email: 'test@example.org',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
client = new GraphQLClient(host, {
|
||||||
|
headers,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given some notifications', () => {
|
describe('given some notifications', () => {
|
||||||
@ -62,24 +67,92 @@ describe('currentUser { notifications }', () => {
|
|||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
factory.create('User', neighborParams),
|
factory.create('User', neighborParams),
|
||||||
factory.create('Notification', { id: 'not-for-you' }),
|
factory.create('Notification', {
|
||||||
factory.create('Notification', { id: 'already-seen', read: true }),
|
id: 'post-mention-not-for-you',
|
||||||
|
}),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'post-mention-already-seen',
|
||||||
|
read: true,
|
||||||
|
}),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'post-mention-unseen',
|
||||||
|
}),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'comment-mention-not-for-you',
|
||||||
|
}),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'comment-mention-already-seen',
|
||||||
|
read: true,
|
||||||
|
}),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'comment-mention-unseen',
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
await factory.create('Notification', { id: 'unseen' })
|
|
||||||
await factory.authenticateAs(neighborParams)
|
await factory.authenticateAs(neighborParams)
|
||||||
await factory.create('Post', { id: 'p1', categoryIds })
|
await factory.create('Post', { id: 'p1', categoryIds })
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
factory.relate('Notification', 'User', { from: 'not-for-you', to: 'neighbor' }),
|
factory.relate('Notification', 'User', {
|
||||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'not-for-you', categoryIds }),
|
from: 'post-mention-not-for-you',
|
||||||
factory.relate('Notification', 'User', { from: 'unseen', to: 'you' }),
|
to: 'neighbor',
|
||||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'unseen', categoryIds }),
|
}),
|
||||||
factory.relate('Notification', 'User', { from: 'already-seen', to: 'you' }),
|
factory.relate('Notification', 'Post', {
|
||||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'already-seen', categoryIds }),
|
from: 'p1',
|
||||||
|
to: 'post-mention-not-for-you',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'User', {
|
||||||
|
from: 'post-mention-unseen',
|
||||||
|
to: 'you',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Post', {
|
||||||
|
from: 'p1',
|
||||||
|
to: 'post-mention-unseen',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'User', {
|
||||||
|
from: 'post-mention-already-seen',
|
||||||
|
to: 'you',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Post', {
|
||||||
|
from: 'p1',
|
||||||
|
to: 'post-mention-already-seen',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
// Comment and its notifications
|
||||||
|
await Promise.all([
|
||||||
|
factory.create('Comment', {
|
||||||
|
id: 'c1',
|
||||||
|
postId: 'p1',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
await Promise.all([
|
||||||
|
factory.relate('Notification', 'User', {
|
||||||
|
from: 'comment-mention-not-for-you',
|
||||||
|
to: 'neighbor',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Comment', {
|
||||||
|
from: 'c1',
|
||||||
|
to: 'comment-mention-not-for-you',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'User', {
|
||||||
|
from: 'comment-mention-unseen',
|
||||||
|
to: 'you',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Comment', {
|
||||||
|
from: 'c1',
|
||||||
|
to: 'comment-mention-unseen',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'User', {
|
||||||
|
from: 'comment-mention-already-seen',
|
||||||
|
to: 'you',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Comment', {
|
||||||
|
from: 'c1',
|
||||||
|
to: 'comment-mention-already-seen',
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('filter for read: false', () => {
|
describe('filter for read: false', () => {
|
||||||
const query = gql`
|
const queryCurrentUserNotificationsFilterRead = gql`
|
||||||
query($read: Boolean) {
|
query($read: Boolean) {
|
||||||
currentUser {
|
currentUser {
|
||||||
notifications(read: $read, orderBy: createdAt_desc) {
|
notifications(read: $read, orderBy: createdAt_desc) {
|
||||||
@ -87,6 +160,9 @@ describe('currentUser { notifications }', () => {
|
|||||||
post {
|
post {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
comment {
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,15 +171,32 @@ describe('currentUser { notifications }', () => {
|
|||||||
it('returns only unread notifications of current user', async () => {
|
it('returns only unread notifications of current user', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
currentUser: {
|
currentUser: {
|
||||||
notifications: [{ id: 'unseen', post: { id: 'p1' } }],
|
notifications: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
id: 'post-mention-unseen',
|
||||||
|
post: {
|
||||||
|
id: 'p1',
|
||||||
|
},
|
||||||
|
comment: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'comment-mention-unseen',
|
||||||
|
post: null,
|
||||||
|
comment: {
|
||||||
|
id: 'c1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await expect(client.request(query, variables)).resolves.toEqual(expected)
|
await expect(
|
||||||
|
client.request(queryCurrentUserNotificationsFilterRead, variables),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('no filters', () => {
|
describe('no filters', () => {
|
||||||
const query = gql`
|
const queryCurrentUserNotifications = gql`
|
||||||
query {
|
query {
|
||||||
currentUser {
|
currentUser {
|
||||||
notifications(orderBy: createdAt_desc) {
|
notifications(orderBy: createdAt_desc) {
|
||||||
@ -111,6 +204,9 @@ describe('currentUser { notifications }', () => {
|
|||||||
post {
|
post {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
comment {
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,13 +214,41 @@ describe('currentUser { notifications }', () => {
|
|||||||
it('returns all notifications of current user', async () => {
|
it('returns all notifications of current user', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
currentUser: {
|
currentUser: {
|
||||||
notifications: [
|
notifications: expect.arrayContaining([
|
||||||
{ id: 'unseen', post: { id: 'p1' } },
|
{
|
||||||
{ id: 'already-seen', post: { id: 'p1' } },
|
id: 'post-mention-unseen',
|
||||||
],
|
post: {
|
||||||
|
id: 'p1',
|
||||||
|
},
|
||||||
|
comment: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'post-mention-already-seen',
|
||||||
|
post: {
|
||||||
|
id: 'p1',
|
||||||
|
},
|
||||||
|
comment: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'comment-mention-unseen',
|
||||||
|
comment: {
|
||||||
|
id: 'c1',
|
||||||
|
},
|
||||||
|
post: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'comment-mention-already-seen',
|
||||||
|
comment: {
|
||||||
|
id: 'c1',
|
||||||
|
},
|
||||||
|
post: null,
|
||||||
|
},
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await expect(client.request(query, variables)).resolves.toEqual(expected)
|
await expect(client.request(queryCurrentUserNotifications, variables)).resolves.toEqual(
|
||||||
|
expected,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -132,7 +256,7 @@ describe('currentUser { notifications }', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('UpdateNotification', () => {
|
describe('UpdateNotification', () => {
|
||||||
const mutation = gql`
|
const mutationUpdateNotification = gql`
|
||||||
mutation($id: ID!, $read: Boolean) {
|
mutation($id: ID!, $read: Boolean) {
|
||||||
UpdateNotification(id: $id, read: $read) {
|
UpdateNotification(id: $id, read: $read) {
|
||||||
id
|
id
|
||||||
@ -140,9 +264,16 @@ describe('UpdateNotification', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const variables = { id: 'to-be-updated', read: true }
|
const variablesPostUpdateNotification = {
|
||||||
|
id: 'post-mention-to-be-updated',
|
||||||
|
read: true,
|
||||||
|
}
|
||||||
|
const variablesCommentUpdateNotification = {
|
||||||
|
id: 'comment-mention-to-be-updated',
|
||||||
|
read: true,
|
||||||
|
}
|
||||||
|
|
||||||
describe('given a notifications', () => {
|
describe('given some notifications', () => {
|
||||||
let headers
|
let headers
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -152,42 +283,105 @@ describe('UpdateNotification', () => {
|
|||||||
password: '1234',
|
password: '1234',
|
||||||
slug: 'mentioned',
|
slug: 'mentioned',
|
||||||
}
|
}
|
||||||
await factory.create('User', mentionedParams)
|
await Promise.all([
|
||||||
await factory.create('Notification', { id: 'to-be-updated' })
|
factory.create('User', mentionedParams),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'post-mention-to-be-updated',
|
||||||
|
}),
|
||||||
|
factory.create('Notification', {
|
||||||
|
id: 'comment-mention-to-be-updated',
|
||||||
|
}),
|
||||||
|
])
|
||||||
await factory.authenticateAs(userParams)
|
await factory.authenticateAs(userParams)
|
||||||
await factory.create('Post', { id: 'p1', categoryIds })
|
await factory.create('Post', { id: 'p1', categoryIds })
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
factory.relate('Notification', 'User', { from: 'to-be-updated', to: 'mentioned-1' }),
|
factory.relate('Notification', 'User', {
|
||||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'to-be-updated' }),
|
from: 'post-mention-to-be-updated',
|
||||||
|
to: 'mentioned-1',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Post', {
|
||||||
|
from: 'p1',
|
||||||
|
to: 'post-mention-to-be-updated',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
// Comment and its notifications
|
||||||
|
await Promise.all([
|
||||||
|
factory.create('Comment', {
|
||||||
|
id: 'c1',
|
||||||
|
postId: 'p1',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
await Promise.all([
|
||||||
|
factory.relate('Notification', 'User', {
|
||||||
|
from: 'comment-mention-to-be-updated',
|
||||||
|
to: 'mentioned-1',
|
||||||
|
}),
|
||||||
|
factory.relate('Notification', 'Comment', {
|
||||||
|
from: 'p1',
|
||||||
|
to: 'comment-mention-to-be-updated',
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
client = new GraphQLClient(host)
|
client = new GraphQLClient(host)
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
await expect(
|
||||||
|
client.request(mutationUpdateNotification, variablesPostUpdateNotification),
|
||||||
|
).rejects.toThrow('Not Authorised')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
headers = await login({
|
||||||
client = new GraphQLClient(host, { headers })
|
email: 'test@example.org',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
client = new GraphQLClient(host, {
|
||||||
|
headers,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
await expect(
|
||||||
|
client.request(mutationUpdateNotification, variablesPostUpdateNotification),
|
||||||
|
).rejects.toThrow('Not Authorised')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('and owner', () => {
|
describe('and owner', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
headers = await login({ email: 'mentioned@example.org', password: '1234' })
|
headers = await login({
|
||||||
client = new GraphQLClient(host, { headers })
|
email: 'mentioned@example.org',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
client = new GraphQLClient(host, {
|
||||||
|
headers,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updates notification', async () => {
|
it('updates post notification', async () => {
|
||||||
const expected = { UpdateNotification: { id: 'to-be-updated', read: true } }
|
const expected = {
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
UpdateNotification: {
|
||||||
|
id: 'post-mention-to-be-updated',
|
||||||
|
read: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await expect(
|
||||||
|
client.request(mutationUpdateNotification, variablesPostUpdateNotification),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates comment notification', async () => {
|
||||||
|
const expected = {
|
||||||
|
UpdateNotification: {
|
||||||
|
id: 'comment-mention-to-be-updated',
|
||||||
|
read: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await expect(
|
||||||
|
client.request(mutationUpdateNotification, variablesCommentUpdateNotification),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -123,4 +123,3 @@ type SharedInboxEndpoint {
|
|||||||
id: ID!
|
id: ID!
|
||||||
uri: String
|
uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,5 +3,6 @@ type Notification {
|
|||||||
read: Boolean
|
read: Boolean
|
||||||
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
||||||
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
||||||
|
comment: Comment @relation(name: "NOTIFIED", direction: "IN")
|
||||||
createdAt: String
|
createdAt: String
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,42 +44,49 @@ import Factory from './factories'
|
|||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
name: 'Peter Lustig',
|
name: 'Peter Lustig',
|
||||||
|
slug: 'peter-lustig',
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
email: 'admin@example.org',
|
email: 'admin@example.org',
|
||||||
}),
|
}),
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
name: 'Bob der Baumeister',
|
name: 'Bob der Baumeister',
|
||||||
|
slug: 'bob-der-baumeister',
|
||||||
role: 'moderator',
|
role: 'moderator',
|
||||||
email: 'moderator@example.org',
|
email: 'moderator@example.org',
|
||||||
}),
|
}),
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u3',
|
id: 'u3',
|
||||||
name: 'Jenny Rostock',
|
name: 'Jenny Rostock',
|
||||||
|
slug: 'jenny-rostock',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
}),
|
}),
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u4',
|
id: 'u4',
|
||||||
name: 'Tick',
|
name: 'Huey (Tick)',
|
||||||
|
slug: 'huey-tick',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'tick@example.org',
|
email: 'huey@example.org',
|
||||||
}),
|
}),
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u5',
|
id: 'u5',
|
||||||
name: 'Trick',
|
name: 'Dewey (Trick)',
|
||||||
|
slug: 'dewey-trick',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'trick@example.org',
|
email: 'dewey@example.org',
|
||||||
}),
|
}),
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u6',
|
id: 'u6',
|
||||||
name: 'Track',
|
name: 'Louie (Track)',
|
||||||
|
slug: 'louie-track',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'track@example.org',
|
email: 'louie@example.org',
|
||||||
}),
|
}),
|
||||||
f.create('User', {
|
f.create('User', {
|
||||||
id: 'u7',
|
id: 'u7',
|
||||||
name: 'Dagobert',
|
name: 'Dagobert',
|
||||||
|
slug: 'dagobert',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
email: 'dagobert@example.org',
|
email: 'dagobert@example.org',
|
||||||
}),
|
}),
|
||||||
@ -99,15 +106,15 @@ import Factory from './factories'
|
|||||||
password: '1234',
|
password: '1234',
|
||||||
}),
|
}),
|
||||||
Factory().authenticateAs({
|
Factory().authenticateAs({
|
||||||
email: 'tick@example.org',
|
email: 'huey@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
}),
|
}),
|
||||||
Factory().authenticateAs({
|
Factory().authenticateAs({
|
||||||
email: 'trick@example.org',
|
email: 'dewey@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
}),
|
}),
|
||||||
Factory().authenticateAs({
|
Factory().authenticateAs({
|
||||||
email: 'track@example.org',
|
email: 'louie@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@ -260,6 +267,10 @@ import Factory from './factories'
|
|||||||
'Hey <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, what\'s up?'
|
'Hey <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, what\'s up?'
|
||||||
const mention2 =
|
const mention2 =
|
||||||
'Hey <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
|
'Hey <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
|
||||||
|
const hashtag1 =
|
||||||
|
'See <a class="hashtag" href="/search/hashtag/NaturphilosophieYoga">#NaturphilosophieYoga</a> can really help you!'
|
||||||
|
const hashtagAndMention1 =
|
||||||
|
'The new physics of <a class="hashtag" href="/search/hashtag/QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" href="/search/hashtag/QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u3" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
asAdmin.create('Post', {
|
asAdmin.create('Post', {
|
||||||
@ -272,6 +283,8 @@ import Factory from './factories'
|
|||||||
}),
|
}),
|
||||||
asUser.create('Post', {
|
asUser.create('Post', {
|
||||||
id: 'p2',
|
id: 'p2',
|
||||||
|
title: `Nature Philosophy Yoga`,
|
||||||
|
content: `${hashtag1}`,
|
||||||
}),
|
}),
|
||||||
asTick.create('Post', {
|
asTick.create('Post', {
|
||||||
id: 'p3',
|
id: 'p3',
|
||||||
@ -293,6 +306,8 @@ import Factory from './factories'
|
|||||||
asUser.create('Post', {
|
asUser.create('Post', {
|
||||||
id: 'p8',
|
id: 'p8',
|
||||||
image: faker.image.unsplash.nature(),
|
image: faker.image.unsplash.nature(),
|
||||||
|
title: `Quantum Flow Theory explains Quantum Gravity`,
|
||||||
|
content: `${hashtagAndMention1}`,
|
||||||
}),
|
}),
|
||||||
asTick.create('Post', {
|
asTick.create('Post', {
|
||||||
id: 'p9',
|
id: 'p9',
|
||||||
@ -639,6 +654,11 @@ import Factory from './factories'
|
|||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const mentionInComment1 =
|
||||||
|
'I heard <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, practice it since 3 years now.'
|
||||||
|
const mentionInComment2 =
|
||||||
|
'Did <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> told you?'
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
asUser.create('Comment', {
|
asUser.create('Comment', {
|
||||||
id: 'c1',
|
id: 'c1',
|
||||||
@ -655,6 +675,12 @@ import Factory from './factories'
|
|||||||
asTrick.create('Comment', {
|
asTrick.create('Comment', {
|
||||||
id: 'c4',
|
id: 'c4',
|
||||||
postId: 'p2',
|
postId: 'p2',
|
||||||
|
content: `${mentionInComment1}`,
|
||||||
|
}),
|
||||||
|
asUser.create('Comment', {
|
||||||
|
id: 'c4-1',
|
||||||
|
postId: 'p2',
|
||||||
|
content: `${mentionInComment2}`,
|
||||||
}),
|
}),
|
||||||
asModerator.create('Comment', {
|
asModerator.create('Comment', {
|
||||||
id: 'c5',
|
id: 'c5',
|
||||||
|
|||||||
@ -3267,10 +3267,10 @@ escodegen@^1.9.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-config-prettier@~6.0.0:
|
eslint-config-prettier@~6.1.0:
|
||||||
version "6.0.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.0.0.tgz#f429a53bde9fc7660e6353910fd996d6284d3c25"
|
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.1.0.tgz#e6f678ba367fbd1273998d5510f76f004e9dce7b"
|
||||||
integrity sha512-vDrcCFE3+2ixNT5H83g28bO/uYAwibJxerXPj+E7op4qzBCsAV36QfvdAyVOoNxKAH2Os/e01T/2x++V0LPukA==
|
integrity sha512-k9fny9sPjIBQ2ftFTesJV21Rg4R/7a7t7LCtZVrYQiHEp8Nnuk3EGaDmsKSAnsPj0BYcgB2zxzHa2NTkIxcOLg==
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stdin "^6.0.0"
|
get-stdin "^6.0.0"
|
||||||
|
|
||||||
@ -3351,10 +3351,10 @@ eslint-plugin-promise@~4.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||||
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||||
|
|
||||||
eslint-plugin-standard@~4.0.0:
|
eslint-plugin-standard@~4.0.1:
|
||||||
version "4.0.0"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
|
||||||
integrity sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA==
|
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
|
||||||
|
|
||||||
eslint-scope@3.7.1:
|
eslint-scope@3.7.1:
|
||||||
version "3.7.1"
|
version "3.7.1"
|
||||||
@ -6163,7 +6163,7 @@ neo-async@^2.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||||
|
|
||||||
neo4j-driver@^1.6.3, neo4j-driver@^1.7.3, neo4j-driver@~1.7.5:
|
neo4j-driver@^1.7.3, neo4j-driver@^1.7.5, neo4j-driver@~1.7.5:
|
||||||
version "1.7.5"
|
version "1.7.5"
|
||||||
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.5.tgz#c3fe3677f69c12f26944563d45e7e7d818a685e4"
|
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.5.tgz#c3fe3677f69c12f26944563d45e7e7d818a685e4"
|
||||||
integrity sha512-xCD2F5+tp/SD9r5avX5bSoY8u8RH2o793xJ9Ikjz1s5qQy7cFxFbbj2c52uz3BVGhRAx/NmB57VjOquYmmxGtw==
|
integrity sha512-xCD2F5+tp/SD9r5avX5bSoY8u8RH2o793xJ9Ikjz1s5qQy7cFxFbbj2c52uz3BVGhRAx/NmB57VjOquYmmxGtw==
|
||||||
@ -6184,14 +6184,14 @@ neo4j-graphql-js@^2.7.1:
|
|||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
neo4j-driver "^1.7.3"
|
neo4j-driver "^1.7.3"
|
||||||
|
|
||||||
neode@^0.3.1:
|
neode@^0.3.2:
|
||||||
version "0.3.1"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.1.tgz#d40147bf20d6951b69c9d392fbdd322aeca07816"
|
resolved "https://registry.yarnpkg.com/neode/-/neode-0.3.2.tgz#ced277e1daba26a77c48f5857c30af054f11c7df"
|
||||||
integrity sha512-SdaJmdjQ3PWOH6W1H8Xgd2CLyJs+BPPXPt0jOVNs7naeQH8nWPP6ixDqI6NWDCxwecTdNl//fpAicB9I6hCwEw==
|
integrity sha512-Bm4GBXdXunv8cqUUkJtksIGHDnYdBJf4UHwzFgXbJiDKBAdqfjhzwAPAhf1PrvlFmR4vJva2Bh/XvIghYOiKrA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^15.1.0"
|
"@hapi/joi" "^15.1.0"
|
||||||
dotenv "^4.0.0"
|
dotenv "^4.0.0"
|
||||||
neo4j-driver "^1.6.3"
|
neo4j-driver "^1.7.5"
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
|
|
||||||
next-tick@^1.0.0:
|
next-tick@^1.0.0:
|
||||||
@ -8552,10 +8552,10 @@ utils-merge@1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
uuid@^3.1.0, uuid@^3.3.2, uuid@~3.3.2:
|
uuid@^3.1.0, uuid@^3.3.2, uuid@~3.3.3:
|
||||||
version "3.3.2"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
|
||||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Given, When, Then } from "cypress-cucumber-preprocessor/steps";
|
import { Given, When, Then } from "cypress-cucumber-preprocessor/steps";
|
||||||
import { getLangByName } from "../../support/helpers";
|
import { getLangByName } from "../../support/helpers";
|
||||||
import slugify from 'slug'
|
import slugify from "slug";
|
||||||
|
|
||||||
/* global cy */
|
/* global cy */
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ let loginCredentials = {
|
|||||||
};
|
};
|
||||||
const narratorParams = {
|
const narratorParams = {
|
||||||
name: "Peter Pan",
|
name: "Peter Pan",
|
||||||
slug: 'peter-pan',
|
slug: "peter-pan",
|
||||||
avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg",
|
avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg",
|
||||||
...loginCredentials
|
...loginCredentials
|
||||||
};
|
};
|
||||||
@ -174,10 +174,10 @@ When("I press {string}", label => {
|
|||||||
|
|
||||||
Given("we have the following posts in our database:", table => {
|
Given("we have the following posts in our database:", table => {
|
||||||
table.hashes().forEach(({ Author, ...postAttributes }, i) => {
|
table.hashes().forEach(({ Author, ...postAttributes }, i) => {
|
||||||
Author = Author || `author-${i}`
|
Author = Author || `author-${i}`;
|
||||||
const userAttributes = {
|
const userAttributes = {
|
||||||
name: Author,
|
name: Author,
|
||||||
email: `${slugify(Author, {lower: true})}@example.org`,
|
email: `${slugify(Author, { lower: true })}@example.org`,
|
||||||
password: "1234"
|
password: "1234"
|
||||||
};
|
};
|
||||||
postAttributes.deleted = Boolean(postAttributes.deleted);
|
postAttributes.deleted = Boolean(postAttributes.deleted);
|
||||||
@ -363,95 +363,106 @@ Then("there are no notifications in the top menu", () => {
|
|||||||
cy.get(".notifications-menu").should("contain", "0");
|
cy.get(".notifications-menu").should("contain", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
Given("there is an annoying user called {string}", (name) => {
|
Given("there is an annoying user called {string}", name => {
|
||||||
const annoyingParams = {
|
const annoyingParams = {
|
||||||
email: 'spammy-spammer@example.org',
|
email: "spammy-spammer@example.org",
|
||||||
password: '1234',
|
password: "1234"
|
||||||
}
|
};
|
||||||
cy.factory().create('User', {
|
cy.factory().create("User", {
|
||||||
...annoyingParams,
|
...annoyingParams,
|
||||||
id: 'annoying-user',
|
id: "annoying-user",
|
||||||
name
|
name
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
Given("I am on the profile page of the annoying user", (name) => {
|
Given("I am on the profile page of the annoying user", name => {
|
||||||
cy.openPage('/profile/annoying-user/spammy-spammer');
|
cy.openPage("/profile/annoying-user/spammy-spammer");
|
||||||
})
|
});
|
||||||
|
|
||||||
When("I visit the profile page of the annoying user", (name) => {
|
When("I visit the profile page of the annoying user", name => {
|
||||||
cy.openPage('/profile/annoying-user');
|
cy.openPage("/profile/annoying-user");
|
||||||
})
|
});
|
||||||
|
|
||||||
When("I ", (name) => {
|
When("I ", name => {
|
||||||
cy.openPage('/profile/annoying-user');
|
cy.openPage("/profile/annoying-user");
|
||||||
})
|
});
|
||||||
|
|
||||||
When("I click on {string} from the content menu in the user info box", (button) => {
|
When(
|
||||||
cy.get('.user-content-menu .content-menu-trigger')
|
"I click on {string} from the content menu in the user info box",
|
||||||
.click()
|
button => {
|
||||||
cy.get('.popover .ds-menu-item-link')
|
cy.get(".user-content-menu .content-menu-trigger").click();
|
||||||
|
cy.get(".popover .ds-menu-item-link")
|
||||||
.contains(button)
|
.contains(button)
|
||||||
.click()
|
.click({ force: true });
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
When ("I navigate to my {string} settings page", (settingsPage) => {
|
When("I navigate to my {string} settings page", settingsPage => {
|
||||||
cy.get(".avatar-menu").click();
|
cy.get(".avatar-menu").click();
|
||||||
cy.get(".avatar-menu-popover")
|
cy.get(".avatar-menu-popover")
|
||||||
.find('a[href]').contains("Settings").click()
|
.find("a[href]")
|
||||||
cy.contains('.ds-menu-item-link', settingsPage).click()
|
.contains("Settings")
|
||||||
})
|
.click();
|
||||||
|
cy.contains(".ds-menu-item-link", settingsPage).click();
|
||||||
|
});
|
||||||
|
|
||||||
Given("I follow the user {string}", (name) => {
|
Given("I follow the user {string}", name => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first('User', { name }).then((followed) => {
|
.first("User", { name })
|
||||||
|
.then(followed => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first('User', {name: narratorParams.name})
|
.first("User", { name: narratorParams.name })
|
||||||
.relateTo(followed, 'following')
|
.relateTo(followed, "following");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
Given("\"Spammy Spammer\" wrote a post {string}", (title) => {
|
Given('"Spammy Spammer" wrote a post {string}', title => {
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.authenticateAs({
|
.authenticateAs({
|
||||||
email: 'spammy-spammer@example.org',
|
email: "spammy-spammer@example.org",
|
||||||
password: '1234',
|
password: "1234"
|
||||||
})
|
})
|
||||||
.create("Post", { title })
|
.create("Post", { title });
|
||||||
})
|
});
|
||||||
|
|
||||||
Then("the list of posts of this user is empty", () => {
|
Then("the list of posts of this user is empty", () => {
|
||||||
cy.get('.ds-card-content').not('.post-link')
|
cy.get(".ds-card-content").not(".post-link");
|
||||||
cy.get('.main-container').find('.ds-space.hc-empty')
|
cy.get(".main-container").find(".ds-space.hc-empty");
|
||||||
})
|
});
|
||||||
|
|
||||||
Then("nobody is following the user profile anymore", () => {
|
Then("nobody is following the user profile anymore", () => {
|
||||||
cy.get('.ds-card-content').not('.post-link')
|
cy.get(".ds-card-content").not(".post-link");
|
||||||
cy.get('.main-container').contains('.ds-card-content', 'is not followed by anyone')
|
cy.get(".main-container").contains(
|
||||||
})
|
".ds-card-content",
|
||||||
|
"is not followed by anyone"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
Given("I wrote a post {string}", (title) => {
|
Given("I wrote a post {string}", title => {
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.authenticateAs(loginCredentials)
|
.authenticateAs(loginCredentials)
|
||||||
.create("Post", { title })
|
.create("Post", { title });
|
||||||
})
|
});
|
||||||
|
|
||||||
When("I block the user {string}", (name) => {
|
When("I block the user {string}", name => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first('User', { name }).then((blocked) => {
|
.first("User", { name })
|
||||||
|
.then(blocked => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first('User', {name: narratorParams.name})
|
.first("User", { name: narratorParams.name })
|
||||||
.relateTo(blocked, 'blocked')
|
.relateTo(blocked, "blocked");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
When("I log in with:", (table) => {
|
When("I log in with:", table => {
|
||||||
const [firstRow] = table.hashes()
|
const [firstRow] = table.hashes();
|
||||||
const { Email, Password } = firstRow
|
const { Email, Password } = firstRow;
|
||||||
cy.login({email: Email, password: Password})
|
cy.login({ email: Email, password: Password });
|
||||||
})
|
});
|
||||||
|
|
||||||
Then("I see only one post with the title {string}", (title) => {
|
Then("I see only one post with the title {string}", title => {
|
||||||
cy.get('.main-container').find('.post-link').should('have.length', 1)
|
cy.get(".main-container")
|
||||||
cy.get('.main-container').contains('.post-link', title)
|
.find(".post-link")
|
||||||
})
|
.should("have.length", 1);
|
||||||
|
cy.get(".main-container").contains(".post-link", title);
|
||||||
|
});
|
||||||
|
|||||||
@ -32,6 +32,8 @@
|
|||||||
value: 1G
|
value: 1G
|
||||||
- name: NEO4J_dbms_memory_heap_max__size
|
- name: NEO4J_dbms_memory_heap_max__size
|
||||||
value: 1G
|
value: 1G
|
||||||
|
- name: NEO4J_dbms_security_procedures_unrestricted
|
||||||
|
value: "algo.*,apoc.*"
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: configmap
|
name: configmap
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</ds-card>
|
</ds-card>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
|
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
|
||||||
<ds-card>
|
<ds-card :id="`commentId-${comment.id}`">
|
||||||
<ds-space margin-bottom="small">
|
<ds-space margin-bottom="small">
|
||||||
<hc-user :user="author" :date-time="comment.createdAt" />
|
<hc-user :user="author" :date-time="comment.createdAt" />
|
||||||
</ds-space>
|
</ds-space>
|
||||||
|
|||||||
@ -24,9 +24,15 @@ describe('CommentForm.vue', () => {
|
|||||||
mutate: jest
|
mutate: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
data: { CreateComment: { contentExcerpt: 'this is a comment' } },
|
data: {
|
||||||
|
CreateComment: {
|
||||||
|
contentExcerpt: 'this is a comment',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.mockRejectedValue({ message: 'Ouch!' }),
|
.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
$toast: {
|
$toast: {
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
@ -34,7 +40,9 @@ describe('CommentForm.vue', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
propsData = {
|
propsData = {
|
||||||
post: { id: 1 },
|
post: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -49,7 +57,12 @@ describe('CommentForm.vue', () => {
|
|||||||
getters,
|
getters,
|
||||||
})
|
})
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(CommentForm, { mocks, localVue, propsData, store })
|
return mount(CommentForm, {
|
||||||
|
mocks,
|
||||||
|
localVue,
|
||||||
|
propsData,
|
||||||
|
store,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -2,7 +2,13 @@
|
|||||||
<ds-form v-show="!editPending" v-model="form" @submit="handleSubmit">
|
<ds-form v-show="!editPending" v-model="form" @submit="handleSubmit">
|
||||||
<template slot-scope="{ errors }">
|
<template slot-scope="{ errors }">
|
||||||
<ds-card>
|
<ds-card>
|
||||||
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
|
<hc-editor
|
||||||
|
ref="editor"
|
||||||
|
:users="users"
|
||||||
|
:hashtags="null"
|
||||||
|
:value="form.content"
|
||||||
|
@input="updateEditorContent"
|
||||||
|
/>
|
||||||
<ds-space />
|
<ds-space />
|
||||||
<ds-flex :gutter="{ base: 'small', md: 'small', sm: 'x-large', xs: 'x-large' }">
|
<ds-flex :gutter="{ base: 'small', md: 'small', sm: 'x-large', xs: 'x-large' }">
|
||||||
<ds-flex-item :width="{ base: '0%', md: '50%', sm: '0%', xs: '0%' }" />
|
<ds-flex-item :width="{ base: '0%', md: '50%', sm: '0%', xs: '0%' }" />
|
||||||
|
|||||||
@ -16,20 +16,7 @@ describe('Editor.vue', () => {
|
|||||||
let mocks
|
let mocks
|
||||||
let getters
|
let getters
|
||||||
|
|
||||||
beforeEach(() => {
|
const Wrapper = () => {
|
||||||
propsData = {}
|
|
||||||
mocks = {
|
|
||||||
$t: () => {},
|
|
||||||
}
|
|
||||||
getters = {
|
|
||||||
'editor/placeholder': () => {
|
|
||||||
return 'some cool placeholder'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
let Wrapper = () => {
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
getters,
|
getters,
|
||||||
})
|
})
|
||||||
@ -45,6 +32,20 @@ describe('Editor.vue', () => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
propsData = {}
|
||||||
|
mocks = {
|
||||||
|
$t: () => {},
|
||||||
|
}
|
||||||
|
getters = {
|
||||||
|
'editor/placeholder': () => {
|
||||||
|
return 'some cool placeholder'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(Wrapper().is('div')).toBe(true)
|
expect(Wrapper().is('div')).toBe(true)
|
||||||
})
|
})
|
||||||
@ -67,5 +68,43 @@ describe('Editor.vue', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('optional extensions', () => {
|
||||||
|
it('sets the Mention items to the users', () => {
|
||||||
|
propsData.users = [
|
||||||
|
{
|
||||||
|
id: 'u345',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
wrapper = Wrapper()
|
||||||
|
expect(wrapper.vm.editor.extensions.options.mention.items()).toEqual(propsData.users)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('mentions is not an option when there are no users', () => {
|
||||||
|
expect(wrapper.vm.editor.extensions.options).toEqual(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
mention: expect.anything(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets the Hashtag items to the hashtags', () => {
|
||||||
|
propsData.hashtags = [
|
||||||
|
{
|
||||||
|
id: 'Frieden',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
wrapper = Wrapper()
|
||||||
|
expect(wrapper.vm.editor.extensions.options.hashtag.items()).toEqual(propsData.hashtags)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hashtags is not an option when there are no hashtags', () => {
|
||||||
|
expect(wrapper.vm.editor.extensions.options).toEqual(
|
||||||
|
expect.not.objectContaining({
|
||||||
|
hashtag: expect.anything(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -200,21 +200,17 @@ export default {
|
|||||||
EditorMenuBubble,
|
EditorMenuBubble,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
users: { type: Array, default: () => [] },
|
users: { type: Array, default: () => null }, // If 'null', than the Mention extention is not assigned.
|
||||||
hashtags: { type: Array, default: () => [] },
|
hashtags: { type: Array, default: () => null }, // If 'null', than the Hashtag extention is not assigned.
|
||||||
value: { type: String, default: '' },
|
value: { type: String, default: '' },
|
||||||
doc: { type: Object, default: () => {} },
|
doc: { type: Object, default: () => {} },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
// Set array of optional extensions by analysing the props.
|
||||||
lastValueHash: null,
|
let optionalExtensions = []
|
||||||
editor: new Editor({
|
// Don't change the following line. The functionallity is in danger!
|
||||||
content: this.value || '',
|
if (this.users) {
|
||||||
doc: this.doc,
|
optionalExtensions.push(
|
||||||
extensions: [
|
|
||||||
...defaultExtensions(this),
|
|
||||||
new EventHandler(),
|
|
||||||
new History(),
|
|
||||||
new Mention({
|
new Mention({
|
||||||
// a list of all suggested items
|
// a list of all suggested items
|
||||||
items: () => {
|
items: () => {
|
||||||
@ -286,6 +282,11 @@ export default {
|
|||||||
return fuse.search(query)
|
return fuse.search(query)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Don't change the following line. The functionallity is in danger!
|
||||||
|
if (this.hashtags) {
|
||||||
|
optionalExtensions.push(
|
||||||
new Hashtag({
|
new Hashtag({
|
||||||
// a list of all suggested items
|
// a list of all suggested items
|
||||||
items: () => {
|
items: () => {
|
||||||
@ -363,6 +364,19 @@ export default {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastValueHash: null,
|
||||||
|
editor: new Editor({
|
||||||
|
content: this.value || '',
|
||||||
|
doc: this.doc,
|
||||||
|
extensions: [
|
||||||
|
...defaultExtensions(this),
|
||||||
|
new EventHandler(),
|
||||||
|
new History(),
|
||||||
|
...optionalExtensions,
|
||||||
],
|
],
|
||||||
onUpdate: e => {
|
onUpdate: e => {
|
||||||
clearTimeout(throttleInputEvent)
|
clearTimeout(throttleInputEvent)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { config, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
import { config, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
||||||
import Notification from '.'
|
import Notification from './Notification'
|
||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
import Filters from '~/plugins/vue-filters'
|
import Filters from '~/plugins/vue-filters'
|
||||||
|
|
||||||
@ -38,6 +38,9 @@ describe('Notification', () => {
|
|||||||
propsData.notification = {
|
propsData.notification = {
|
||||||
post: {
|
post: {
|
||||||
title: "It's a title",
|
title: "It's a title",
|
||||||
|
id: 'post-1',
|
||||||
|
slug: 'its-a-title',
|
||||||
|
contentExcerpt: '<a href="/profile/u3" target="_blank">@jenny-rostock</a> is the best',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -46,6 +49,10 @@ describe('Notification', () => {
|
|||||||
expect(Wrapper().text()).toContain("It's a title")
|
expect(Wrapper().text()).toContain("It's a title")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders the contentExcerpt', () => {
|
||||||
|
expect(Wrapper().text()).toContain('@jenny-rostock is the best')
|
||||||
|
})
|
||||||
|
|
||||||
it('has no class "read"', () => {
|
it('has no class "read"', () => {
|
||||||
expect(Wrapper().classes()).not.toContain('read')
|
expect(Wrapper().classes()).not.toContain('read')
|
||||||
})
|
})
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<ds-space :class="[{ read: notification.read }, notification]" margin-bottom="x-small">
|
||||||
|
<no-ssr>
|
||||||
|
<ds-space margin-bottom="x-small">
|
||||||
|
<hc-user
|
||||||
|
v-if="resourceType == 'Post'"
|
||||||
|
:user="post.author"
|
||||||
|
:date-time="post.createdAt"
|
||||||
|
:trunc="35"
|
||||||
|
/>
|
||||||
|
<hc-user v-else :user="comment.author" :date-time="comment.createdAt" :trunc="35" />
|
||||||
|
</ds-space>
|
||||||
|
<ds-text color="soft">
|
||||||
|
{{ $t('notifications.menu.mentioned', { resource: resourceType }) }}
|
||||||
|
</ds-text>
|
||||||
|
</no-ssr>
|
||||||
|
<ds-space margin-bottom="x-small" />
|
||||||
|
<nuxt-link
|
||||||
|
class="notification-mention-post"
|
||||||
|
:to="{ name: 'post-id-slug', params, ...hashParam }"
|
||||||
|
@click.native="$emit('read')"
|
||||||
|
>
|
||||||
|
<ds-space margin-bottom="x-small">
|
||||||
|
<ds-card
|
||||||
|
:header="post.title || comment.post.title"
|
||||||
|
hover
|
||||||
|
space="x-small"
|
||||||
|
class="notifications-card"
|
||||||
|
>
|
||||||
|
<ds-space margin-bottom="x-small" />
|
||||||
|
<div v-if="resourceType == 'Post'">{{ post.contentExcerpt | removeHtml }}</div>
|
||||||
|
<div v-else>
|
||||||
|
<span class="comment-notification-header">Comment:</span>
|
||||||
|
{{ comment.contentExcerpt | removeHtml }}
|
||||||
|
</div>
|
||||||
|
</ds-card>
|
||||||
|
</ds-space>
|
||||||
|
</nuxt-link>
|
||||||
|
</ds-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HcUser from '~/components/User'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Notification',
|
||||||
|
components: {
|
||||||
|
HcUser,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
notification: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
resourceType() {
|
||||||
|
return this.post.id ? 'Post' : 'Comment'
|
||||||
|
},
|
||||||
|
post() {
|
||||||
|
return this.notification.post || {}
|
||||||
|
},
|
||||||
|
comment() {
|
||||||
|
return this.notification.comment || {}
|
||||||
|
},
|
||||||
|
params() {
|
||||||
|
return {
|
||||||
|
id: this.post.id || this.comment.post.id,
|
||||||
|
slug: this.post.slug || this.comment.post.slug,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hashParam() {
|
||||||
|
return this.post.id ? {} : { hash: `#commentId-${this.comment.id}` }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.notification.read {
|
||||||
|
opacity: 0.6; /* Real browsers */
|
||||||
|
filter: alpha(opacity = 60); /* MSIE */
|
||||||
|
}
|
||||||
|
.notifications-card {
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
.comment-notification-header {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 0.1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ds-space :class="{ notification: true, read: notification.read }" margin-bottom="x-small">
|
|
||||||
<no-ssr>
|
|
||||||
<ds-space margin-bottom="x-small">
|
|
||||||
<hc-user :user="post.author" :date-time="post.createdAt" :trunc="35" />
|
|
||||||
</ds-space>
|
|
||||||
<ds-text color="soft">
|
|
||||||
{{ $t('notifications.menu.mentioned') }}
|
|
||||||
</ds-text>
|
|
||||||
</no-ssr>
|
|
||||||
<ds-space margin-bottom="x-small" />
|
|
||||||
<nuxt-link
|
|
||||||
class="notification-mention-post"
|
|
||||||
:to="{ name: 'post-id-slug', params: { id: post.id, slug: post.slug } }"
|
|
||||||
@click.native="$emit('read')"
|
|
||||||
>
|
|
||||||
<ds-space margin-bottom="x-small">
|
|
||||||
<ds-card :header="post.title" :image="post.image" hover space="x-small">
|
|
||||||
<ds-space margin-bottom="x-small" />
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
|
||||||
<div v-html="excerpt" />
|
|
||||||
<!-- eslint-enable vue/no-v-html -->
|
|
||||||
</ds-card>
|
|
||||||
</ds-space>
|
|
||||||
</nuxt-link>
|
|
||||||
</ds-space>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import HcUser from '~/components/User'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Notification',
|
|
||||||
components: {
|
|
||||||
HcUser,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
notification: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
excerpt() {
|
|
||||||
return this.$filters.removeLinks(this.post.contentExcerpt)
|
|
||||||
},
|
|
||||||
post() {
|
|
||||||
return this.notification.post || {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.notification.read {
|
|
||||||
opacity: 0.6; /* Real browsers */
|
|
||||||
filter: alpha(opacity = 60); /* MSIE */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { config, shallowMount, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
import { config, shallowMount, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
||||||
import NotificationList from '.'
|
import NotificationList from './NotificationList'
|
||||||
import Notification from '../Notification'
|
import Notification from '../Notification/Notification'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Filters from '~/plugins/vue-filters'
|
import Filters from '~/plugins/vue-filters'
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ describe('NotificationList.vue', () => {
|
|||||||
post: {
|
post: {
|
||||||
id: 'post-1',
|
id: 'post-1',
|
||||||
title: 'some post title',
|
title: 'some post title',
|
||||||
|
slug: 'some-post-title',
|
||||||
contentExcerpt: 'this is a post content',
|
contentExcerpt: 'this is a post content',
|
||||||
author: {
|
author: {
|
||||||
id: 'john-1',
|
id: 'john-1',
|
||||||
@ -59,6 +60,7 @@ describe('NotificationList.vue', () => {
|
|||||||
post: {
|
post: {
|
||||||
id: 'post-2',
|
id: 'post-2',
|
||||||
title: 'another post title',
|
title: 'another post title',
|
||||||
|
slug: 'another-post-title',
|
||||||
contentExcerpt: 'this is yet another post content',
|
contentExcerpt: 'this is yet another post content',
|
||||||
author: {
|
author: {
|
||||||
id: 'john-1',
|
id: 'john-1',
|
||||||
@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Notification from '../Notification'
|
import Notification from '../Notification/Notification'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NotificationList',
|
name: 'NotificationList',
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { config, shallowMount, createLocalVue } from '@vue/test-utils'
|
import { config, shallowMount, createLocalVue } from '@vue/test-utils'
|
||||||
import NotificationMenu from '.'
|
import NotificationMenu from './NotificationMenu'
|
||||||
|
|
||||||
import Styleguide from '@human-connection/styleguide'
|
import Styleguide from '@human-connection/styleguide'
|
||||||
import Filters from '~/plugins/vue-filters'
|
import Filters from '~/plugins/vue-filters'
|
||||||
@ -17,30 +17,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NotificationList from '../NotificationList'
|
|
||||||
import Dropdown from '~/components/Dropdown'
|
import Dropdown from '~/components/Dropdown'
|
||||||
import gql from 'graphql-tag'
|
import { currentUserNotificationsQuery, updateNotificationMutation } from '~/graphql/User'
|
||||||
|
import NotificationList from '../NotificationList/NotificationList'
|
||||||
const MARK_AS_READ = gql(`
|
|
||||||
mutation($id: ID!, $read: Boolean!) {
|
|
||||||
UpdateNotification(id: $id, read: $read) {
|
|
||||||
id
|
|
||||||
read
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
const NOTIFICATIONS = gql(`{
|
|
||||||
currentUser {
|
|
||||||
id
|
|
||||||
notifications(read: false, orderBy: createdAt_desc) {
|
|
||||||
id read createdAt
|
|
||||||
post {
|
|
||||||
id createdAt disabled deleted title contentExcerpt slug
|
|
||||||
author { id slug name disabled deleted }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NotificationMenu',
|
name: 'NotificationMenu',
|
||||||
@ -61,7 +40,7 @@ export default {
|
|||||||
const variables = { id: notificationId, read: true }
|
const variables = { id: notificationId, read: true }
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: MARK_AS_READ,
|
mutation: updateNotificationMutation(),
|
||||||
variables,
|
variables,
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -71,7 +50,7 @@ export default {
|
|||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
notifications: {
|
notifications: {
|
||||||
query: NOTIFICATIONS,
|
query: currentUserNotificationsQuery(),
|
||||||
update: data => {
|
update: data => {
|
||||||
const {
|
const {
|
||||||
currentUser: { notifications },
|
currentUser: { notifications },
|
||||||
@ -74,3 +74,78 @@ export default i18n => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const currentUserNotificationsQuery = () => {
|
||||||
|
return gql`
|
||||||
|
{
|
||||||
|
currentUser {
|
||||||
|
id
|
||||||
|
notifications(read: false, orderBy: createdAt_desc) {
|
||||||
|
id
|
||||||
|
read
|
||||||
|
createdAt
|
||||||
|
post {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
title
|
||||||
|
contentExcerpt
|
||||||
|
slug
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comment {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
contentExcerpt
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
title
|
||||||
|
contentExcerpt
|
||||||
|
slug
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateNotificationMutation = () => {
|
||||||
|
return gql`
|
||||||
|
mutation($id: ID!, $read: Boolean!) {
|
||||||
|
UpdateNotification(id: $id, read: $read) {
|
||||||
|
id
|
||||||
|
read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|||||||
@ -151,7 +151,7 @@ import { mapGetters, mapActions, mapMutations } from 'vuex'
|
|||||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||||
import SearchInput from '~/components/SearchInput.vue'
|
import SearchInput from '~/components/SearchInput.vue'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import NotificationMenu from '~/components/notifications/NotificationMenu'
|
import NotificationMenu from '~/components/notifications/NotificationMenu/NotificationMenu'
|
||||||
import Dropdown from '~/components/Dropdown'
|
import Dropdown from '~/components/Dropdown'
|
||||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
||||||
import seo from '~/mixins/seo'
|
import seo from '~/mixins/seo'
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"menu": {
|
"menu": {
|
||||||
"mentioned": "hat dich in einem Beitrag erwähnt"
|
"mentioned": "hat dich in einem {resource} erwähnt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"menu": {
|
"menu": {
|
||||||
"mentioned": "mentioned you in a post"
|
"mentioned": "mentioned you in a {resource}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
|
|||||||
@ -57,11 +57,26 @@ module.exports = {
|
|||||||
title: 'Human Connection',
|
title: 'Human Connection',
|
||||||
titleTemplate: '%s - Human Connection',
|
titleTemplate: '%s - Human Connection',
|
||||||
meta: [
|
meta: [
|
||||||
{ charset: 'utf-8' },
|
{
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
charset: 'utf-8',
|
||||||
{ hid: 'description', name: 'description', content: pkg.description },
|
},
|
||||||
|
{
|
||||||
|
name: 'viewport',
|
||||||
|
content: 'width=device-width, initial-scale=1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hid: 'description',
|
||||||
|
name: 'description',
|
||||||
|
content: pkg.description,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
link: [
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/x-icon',
|
||||||
|
href: '/favicon.ico',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -107,8 +122,79 @@ module.exports = {
|
|||||||
middleware: ['authenticated'],
|
middleware: ['authenticated'],
|
||||||
linkActiveClass: 'router-link-active',
|
linkActiveClass: 'router-link-active',
|
||||||
linkExactActiveClass: 'router-link-exact-active',
|
linkExactActiveClass: 'router-link-exact-active',
|
||||||
scrollBehavior: () => {
|
scrollBehavior: (to, _from, savedPosition) => {
|
||||||
return { x: 0, y: 0 }
|
let position = false
|
||||||
|
// if no children detected and scrollToTop is not explicitly disabled
|
||||||
|
if (
|
||||||
|
to.matched.length < 2 &&
|
||||||
|
to.matched.every(r => r.components.default.options.scrollToTop !== false)
|
||||||
|
) {
|
||||||
|
// scroll to the top of the page
|
||||||
|
position = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
}
|
||||||
|
} else if (to.matched.some(r => r.components.default.options.scrollToTop)) {
|
||||||
|
// if one of the children has scrollToTop option set to true
|
||||||
|
position = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// savedPosition is only available for popstate navigations (back button)
|
||||||
|
if (savedPosition) {
|
||||||
|
position = savedPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
// wait for the out transition to complete (if necessary)
|
||||||
|
window.$nuxt.$once('triggerScroll', () => {
|
||||||
|
let processInterval = null
|
||||||
|
let processTime = 0
|
||||||
|
const callInterval = 100
|
||||||
|
const callIntervalLimit = 2000
|
||||||
|
|
||||||
|
// coords will be used if no selector is provided,
|
||||||
|
// or if the selector didn't match any element.
|
||||||
|
if (to.hash) {
|
||||||
|
let hash = to.hash
|
||||||
|
// CSS.escape() is not supported with IE and Edge.
|
||||||
|
if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') {
|
||||||
|
hash = '#' + window.CSS.escape(hash.substr(1))
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
processInterval = setInterval(() => {
|
||||||
|
const hashIsFound = document.querySelector(hash)
|
||||||
|
|
||||||
|
if (hashIsFound) {
|
||||||
|
position = {
|
||||||
|
selector: hash,
|
||||||
|
offset: { x: 0, y: -500 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processTime += callInterval
|
||||||
|
if (hashIsFound || processTime >= callIntervalLimit) {
|
||||||
|
clearInterval(processInterval)
|
||||||
|
processInterval = null
|
||||||
|
}
|
||||||
|
}, callInterval)
|
||||||
|
} catch (e) {
|
||||||
|
/* eslint-disable-next-line no-console */
|
||||||
|
console.warn(
|
||||||
|
'Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolveInterval = setInterval(() => {
|
||||||
|
if (!processInterval) {
|
||||||
|
clearInterval(resolveInterval)
|
||||||
|
resolve(position)
|
||||||
|
}
|
||||||
|
}, callInterval)
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -116,8 +202,18 @@ module.exports = {
|
|||||||
** Nuxt.js modules
|
** Nuxt.js modules
|
||||||
*/
|
*/
|
||||||
modules: [
|
modules: [
|
||||||
['@nuxtjs/dotenv', { only: envWhitelist }],
|
[
|
||||||
['nuxt-env', { keys: envWhitelist }],
|
'@nuxtjs/dotenv',
|
||||||
|
{
|
||||||
|
only: envWhitelist,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nuxt-env',
|
||||||
|
{
|
||||||
|
keys: envWhitelist,
|
||||||
|
},
|
||||||
|
],
|
||||||
'cookie-universal-nuxt',
|
'cookie-universal-nuxt',
|
||||||
'@nuxtjs/apollo',
|
'@nuxtjs/apollo',
|
||||||
'@nuxtjs/axios',
|
'@nuxtjs/axios',
|
||||||
@ -155,7 +251,9 @@ module.exports = {
|
|||||||
'/api': {
|
'/api': {
|
||||||
// make this configurable (nuxt-dotenv)
|
// make this configurable (nuxt-dotenv)
|
||||||
target: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
target: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
||||||
pathRewrite: { '^/api': '' },
|
pathRewrite: {
|
||||||
|
'^/api': '',
|
||||||
|
},
|
||||||
toProxy: true, // cloudflare needs that
|
toProxy: true, // cloudflare needs that
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
|
|||||||
@ -101,7 +101,7 @@
|
|||||||
"core-js": "~2.6.9",
|
"core-js": "~2.6.9",
|
||||||
"css-loader": "~2.1.1",
|
"css-loader": "~2.1.1",
|
||||||
"eslint": "~5.16.0",
|
"eslint": "~5.16.0",
|
||||||
"eslint-config-prettier": "~6.0.0",
|
"eslint-config-prettier": "~6.1.0",
|
||||||
"eslint-config-standard": "~12.0.0",
|
"eslint-config-standard": "~12.0.0",
|
||||||
"eslint-loader": "~2.2.1",
|
"eslint-loader": "~2.2.1",
|
||||||
"eslint-plugin-import": "~2.18.2",
|
"eslint-plugin-import": "~2.18.2",
|
||||||
@ -109,7 +109,7 @@
|
|||||||
"eslint-plugin-node": "~9.1.0",
|
"eslint-plugin-node": "~9.1.0",
|
||||||
"eslint-plugin-prettier": "~3.1.0",
|
"eslint-plugin-prettier": "~3.1.0",
|
||||||
"eslint-plugin-promise": "~4.2.1",
|
"eslint-plugin-promise": "~4.2.1",
|
||||||
"eslint-plugin-standard": "~4.0.0",
|
"eslint-plugin-standard": "~4.0.1",
|
||||||
"eslint-plugin-vue": "~5.2.3",
|
"eslint-plugin-vue": "~5.2.3",
|
||||||
"flush-promises": "^1.0.2",
|
"flush-promises": "^1.0.2",
|
||||||
"fuse.js": "^3.4.5",
|
"fuse.js": "^3.4.5",
|
||||||
|
|||||||
@ -83,6 +83,15 @@ export default ({ app = {} }) => {
|
|||||||
|
|
||||||
return excerpt
|
return excerpt
|
||||||
},
|
},
|
||||||
|
removeHtml: content => {
|
||||||
|
if (!content) return ''
|
||||||
|
// replace linebreaks with spaces first
|
||||||
|
let contentExcerpt = content.replace(/<br>/gim, ' ').trim()
|
||||||
|
// remove the rest of the HTML
|
||||||
|
contentExcerpt = contentExcerpt.replace(/<(?:.|\n)*?>/gm, '').trim()
|
||||||
|
|
||||||
|
return contentExcerpt
|
||||||
|
},
|
||||||
proxyApiUrl: url => {
|
proxyApiUrl: url => {
|
||||||
if (!url) return url
|
if (!url) return url
|
||||||
return url.startsWith('/') ? url.replace('/', '/api/') : url
|
return url.startsWith('/') ? url.replace('/', '/api/') : url
|
||||||
|
|||||||
@ -69,7 +69,8 @@ export const actions = {
|
|||||||
const {
|
const {
|
||||||
data: { currentUser },
|
data: { currentUser },
|
||||||
} = await client.query({
|
} = await client.query({
|
||||||
query: gql(`{
|
query: gql`
|
||||||
|
query {
|
||||||
currentUser {
|
currentUser {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -103,7 +104,8 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`),
|
}
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
if (!currentUser) return dispatch('logout')
|
if (!currentUser) return dispatch('logout')
|
||||||
commit('SET_USER', currentUser)
|
commit('SET_USER', currentUser)
|
||||||
@ -122,7 +124,10 @@ export const actions = {
|
|||||||
login(email: $email, password: $password)
|
login(email: $email, password: $password)
|
||||||
}
|
}
|
||||||
`),
|
`),
|
||||||
variables: { email, password },
|
variables: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
await this.app.$apolloHelpers.onLogin(login)
|
await this.app.$apolloHelpers.onLogin(login)
|
||||||
commit('SET_TOKEN', login)
|
commit('SET_TOKEN', login)
|
||||||
|
|||||||
@ -19,7 +19,10 @@ export const mutations = {
|
|||||||
const toBeUpdated = notifications.find(n => {
|
const toBeUpdated = notifications.find(n => {
|
||||||
return n.id === notification.id
|
return n.id === notification.id
|
||||||
})
|
})
|
||||||
state.notifications = { ...toBeUpdated, ...notification }
|
state.notifications = {
|
||||||
|
...toBeUpdated,
|
||||||
|
...notification,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
export const getters = {
|
export const getters = {
|
||||||
@ -38,7 +41,8 @@ export const actions = {
|
|||||||
const {
|
const {
|
||||||
data: { currentUser },
|
data: { currentUser },
|
||||||
} = await client.query({
|
} = await client.query({
|
||||||
query: gql(`{
|
query: gql`
|
||||||
|
{
|
||||||
currentUser {
|
currentUser {
|
||||||
id
|
id
|
||||||
notifications(orderBy: createdAt_desc) {
|
notifications(orderBy: createdAt_desc) {
|
||||||
@ -59,7 +63,8 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`),
|
}
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
notifications = currentUser.notifications
|
notifications = currentUser.notifications
|
||||||
commit('SET_NOTIFICATIONS', notifications)
|
commit('SET_NOTIFICATIONS', notifications)
|
||||||
@ -71,18 +76,24 @@ export const actions = {
|
|||||||
|
|
||||||
async markAsRead({ commit, rootGetters }, notificationId) {
|
async markAsRead({ commit, rootGetters }, notificationId) {
|
||||||
const client = this.app.apolloProvider.defaultClient
|
const client = this.app.apolloProvider.defaultClient
|
||||||
const mutation = gql(`
|
const mutation = gql`
|
||||||
mutation($id: ID!, $read: Boolean!) {
|
mutation($id: ID!, $read: Boolean!) {
|
||||||
UpdateNotification(id: $id, read: $read) {
|
UpdateNotification(id: $id, read: $read) {
|
||||||
id
|
id
|
||||||
read
|
read
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`
|
||||||
const variables = { id: notificationId, read: true }
|
const variables = {
|
||||||
|
id: notificationId,
|
||||||
|
read: true,
|
||||||
|
}
|
||||||
const {
|
const {
|
||||||
data: { UpdateNotification },
|
data: { UpdateNotification },
|
||||||
} = await client.mutate({ mutation, variables })
|
} = await client.mutate({
|
||||||
|
mutation,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
commit('UPDATE_NOTIFICATIONS', UpdateNotification)
|
commit('UPDATE_NOTIFICATIONS', UpdateNotification)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6168,10 +6168,10 @@ escodegen@^1.9.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-config-prettier@^6.0.0, eslint-config-prettier@~6.0.0:
|
eslint-config-prettier@^6.0.0, eslint-config-prettier@~6.1.0:
|
||||||
version "6.0.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.0.0.tgz#f429a53bde9fc7660e6353910fd996d6284d3c25"
|
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.1.0.tgz#e6f678ba367fbd1273998d5510f76f004e9dce7b"
|
||||||
integrity sha512-vDrcCFE3+2ixNT5H83g28bO/uYAwibJxerXPj+E7op4qzBCsAV36QfvdAyVOoNxKAH2Os/e01T/2x++V0LPukA==
|
integrity sha512-k9fny9sPjIBQ2ftFTesJV21Rg4R/7a7t7LCtZVrYQiHEp8Nnuk3EGaDmsKSAnsPj0BYcgB2zxzHa2NTkIxcOLg==
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stdin "^6.0.0"
|
get-stdin "^6.0.0"
|
||||||
|
|
||||||
@ -6263,10 +6263,10 @@ eslint-plugin-promise@~4.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||||
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||||
|
|
||||||
eslint-plugin-standard@~4.0.0:
|
eslint-plugin-standard@~4.0.1:
|
||||||
version "4.0.0"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
|
||||||
integrity sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA==
|
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
|
||||||
|
|
||||||
eslint-plugin-vue@~5.2.3:
|
eslint-plugin-vue@~5.2.3:
|
||||||
version "5.2.3"
|
version "5.2.3"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user