Merge branch 'master' into 375-Sorting-II

This commit is contained in:
Alexander Friedland 2019-07-19 11:53:01 +02:00 committed by GitHub
commit 4fe3c08402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 511 additions and 147 deletions

View File

@ -89,7 +89,7 @@
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.5.4",
"@babel/register": "~7.4.4",
"apollo-server-testing": "~2.6.9",
"apollo-server-testing": "~2.7.0",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.2",
"babel-jest": "~24.8.0",
@ -99,7 +99,7 @@
"eslint-config-prettier": "~6.0.0",
"eslint-config-standard": "~12.0.0",
"eslint-plugin-import": "~2.18.0",
"eslint-plugin-jest": "~22.8.0",
"eslint-plugin-jest": "~22.9.0",
"eslint-plugin-node": "~9.1.0",
"eslint-plugin-prettier": "~3.1.0",
"eslint-plugin-promise": "~4.2.1",

View File

@ -176,6 +176,7 @@ const permissions = shield(
enable: isModerator,
disable: isModerator,
CreateComment: isAuthenticated,
UpdateComment: isAuthor,
DeleteComment: isAuthor,
DeleteUser: isDeletingOwnAccount,
requestPasswordReset: allow,

View File

@ -1,18 +0,0 @@
import { UserInputError } from 'apollo-server'
const validateUrl = async (resolve, root, args, context, info) => {
const { url } = args
const isValid = url.match(/^(?:https?:\/\/)(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g)
if (isValid) {
/* eslint-disable-next-line no-return-await */
return await resolve(root, args, context, info)
} else {
throw new UserInputError('Input is not a URL')
}
}
export default {
Mutation: {
CreateSocialMedia: validateUrl,
},
}

View File

@ -45,9 +45,20 @@ const validateCommentCreation = async (resolve, root, args, context, info) => {
}
}
const validateUpdateComment = async (resolve, root, args, context, info) => {
const COMMENT_MIN_LENGTH = 1
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
if (!args.content || content.length < COMMENT_MIN_LENGTH) {
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
}
return resolve(root, args, context, info)
}
export default {
Mutation: {
CreateSocialMedia: validate(socialMediaSchema),
CreateComment: validateCommentCreation,
UpdateComment: validateUpdateComment,
},
}

View File

@ -9,7 +9,28 @@ let createPostVariables
let createCommentVariablesSansPostId
let createCommentVariablesWithNonExistentPost
let userParams
let authorParams
let headers
const createPostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!) {
CreatePost(id: $id, title: $title, content: $content) {
id
}
}
`
const createCommentMutation = gql`
mutation($id: ID, $postId: ID!, $content: String!) {
CreateComment(id: $id, postId: $postId, content: $content) {
id
content
}
}
`
createPostVariables = {
id: 'p1',
title: 'post to comment on',
content: 'please comment on me',
}
beforeEach(async () => {
userParams = {
@ -25,21 +46,6 @@ afterEach(async () => {
})
describe('CreateComment', () => {
const createCommentMutation = gql`
mutation($postId: ID!, $content: String!) {
CreateComment(postId: $postId, content: $content) {
id
content
}
}
`
const createPostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!) {
CreatePost(id: $id, title: $title, content: $content) {
id
}
}
`
describe('unauthenticated', () => {
it('throws authorization error', async () => {
createCommentVariables = {
@ -54,7 +60,6 @@ describe('CreateComment', () => {
})
describe('authenticated', () => {
let headers
beforeEach(async () => {
headers = await login(userParams)
client = new GraphQLClient(host, {
@ -64,11 +69,6 @@ describe('CreateComment', () => {
postId: 'p1',
content: "I'm authorised to comment",
}
createPostVariables = {
id: 'p1',
title: 'post to comment on',
content: 'please comment on me',
}
await client.request(createPostMutation, createPostVariables)
})
@ -187,19 +187,8 @@ describe('CreateComment', () => {
})
})
describe('DeleteComment', () => {
const deleteCommentMutation = gql`
mutation($id: ID!) {
DeleteComment(id: $id) {
id
}
}
`
let deleteCommentVariables = {
id: 'c1',
}
describe('ManageComments', () => {
let authorParams
beforeEach(async () => {
authorParams = {
email: 'author@example.org',
@ -213,51 +202,178 @@ describe('DeleteComment', () => {
content: 'Post to be commented',
})
await asAuthor.create('Comment', {
id: 'c1',
id: 'c456',
postId: 'p1',
content: 'Comment to be deleted',
})
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(deleteCommentMutation, deleteCommentVariables)).rejects.toThrow(
'Not Authorised',
)
})
})
describe('authenticated but not the author', () => {
beforeEach(async () => {
let headers
headers = await login(userParams)
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(client.request(deleteCommentMutation, deleteCommentVariables)).rejects.toThrow(
'Not Authorised',
)
})
})
describe('authenticated as author', () => {
beforeEach(async () => {
let headers
headers = await login(authorParams)
client = new GraphQLClient(host, { headers })
})
it('deletes the comment', async () => {
const expected = {
DeleteComment: {
id: 'c1',
},
describe('UpdateComment', () => {
const updateCommentMutation = gql`
mutation($content: String!, $id: ID!) {
UpdateComment(content: $content, id: $id) {
id
content
}
}
await expect(client.request(deleteCommentMutation, deleteCommentVariables)).resolves.toEqual(
expected,
)
`
let updateCommentVariables = {
id: 'c456',
content: 'The comment is updated',
}
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(updateCommentMutation, updateCommentVariables)).rejects.toThrow(
'Not Authorised',
)
})
})
describe('authenticated but not the author', () => {
beforeEach(async () => {
headers = await login({
email: 'test@example.org',
password: '1234',
})
client = new GraphQLClient(host, {
headers,
})
})
it('throws authorization error', async () => {
await expect(client.request(updateCommentMutation, updateCommentVariables)).rejects.toThrow(
'Not Authorised',
)
})
})
describe('authenticated as author', () => {
beforeEach(async () => {
headers = await login(authorParams)
client = new GraphQLClient(host, {
headers,
})
})
it('updates the comment', async () => {
const expected = {
UpdateComment: {
id: 'c456',
content: 'The comment is updated',
},
}
await expect(
client.request(updateCommentMutation, updateCommentVariables),
).resolves.toEqual(expected)
})
it('throw an error if an empty string is sent from the editor as content', async () => {
updateCommentVariables = {
id: 'c456',
content: '<p></p>',
}
await expect(client.request(updateCommentMutation, updateCommentVariables)).rejects.toThrow(
'Comment must be at least 1 character long!',
)
})
it('throws an error if a comment sent from the editor does not contain a single letter character', async () => {
updateCommentVariables = {
id: 'c456',
content: '<p> </p>',
}
await expect(client.request(updateCommentMutation, updateCommentVariables)).rejects.toThrow(
'Comment must be at least 1 character long!',
)
})
it('throws an error if commentId is sent as an empty string', async () => {
updateCommentVariables = {
id: '',
content: '<p>Hello</p>',
}
await expect(client.request(updateCommentMutation, updateCommentVariables)).rejects.toThrow(
'Not Authorised!',
)
})
it('throws an error if the comment does not exist in the database', async () => {
updateCommentVariables = {
id: 'c1000',
content: '<p>Hello</p>',
}
await expect(client.request(updateCommentMutation, updateCommentVariables)).rejects.toThrow(
'Not Authorised!',
)
})
})
})
describe('DeleteComment', () => {
const deleteCommentMutation = gql`
mutation($id: ID!) {
DeleteComment(id: $id) {
id
}
}
`
let deleteCommentVariables = {
id: 'c456',
}
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(deleteCommentMutation, deleteCommentVariables)).rejects.toThrow(
'Not Authorised',
)
})
})
describe('authenticated but not the author', () => {
beforeEach(async () => {
headers = await login({
email: 'test@example.org',
password: '1234',
})
client = new GraphQLClient(host, {
headers,
})
})
it('throws authorization error', async () => {
await expect(client.request(deleteCommentMutation, deleteCommentVariables)).rejects.toThrow(
'Not Authorised',
)
})
})
describe('authenticated as author', () => {
beforeEach(async () => {
headers = await login(authorParams)
client = new GraphQLClient(host, {
headers,
})
})
it('deletes the comment', async () => {
const expected = {
DeleteComment: {
id: 'c456',
},
}
await expect(
client.request(deleteCommentMutation, deleteCommentVariables),
).resolves.toEqual(expected)
})
})
})
})

View File

@ -24,7 +24,7 @@ type Mutation {
): Comment
UpdateComment(
id: ID!
content: String
content: String!
contentExcerpt: String
deleted: Boolean
disabled: Boolean

View File

@ -1336,6 +1336,14 @@ apollo-cache-control@0.7.5:
apollo-server-env "2.4.0"
graphql-extensions "0.7.7"
apollo-cache-control@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.0.tgz#08b157e5f8cd86f63608b05d45222de0725ebd5a"
integrity sha512-BBnfUmSWRws5dRSDD+R56RLJCE9v6xQuob+i/1Ju9EX4LZszU5JKVmxEvnkJ1bk/BkihjoQXTnP6fJCnt6fCmA==
dependencies:
apollo-server-env "2.4.0"
graphql-extensions "0.8.0"
apollo-cache-inmemory@~1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.2.tgz#bbf2e4e1eacdf82b2d526f5c2f3b37e5acee3c5e"
@ -1377,6 +1385,14 @@ apollo-datasource@0.5.0:
apollo-server-caching "0.4.0"
apollo-server-env "2.4.0"
apollo-datasource@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.6.0.tgz#823d6be8a3804613b5c56d2972c07db662293fc6"
integrity sha512-DOzzYWEOReYRu2vWPKEulqlTb9Xjg67sjVCzve5MXa7GUXjfr8IKioljvfoBMlqm/PpbJVk2ci4n5NIFqoYsrQ==
dependencies:
apollo-server-caching "0.5.0"
apollo-server-env "2.4.0"
apollo-engine-reporting-protobuf@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.3.1.tgz#a581257fa8e3bb115ce38bf1b22e052d1475ad69"
@ -1384,6 +1400,13 @@ apollo-engine-reporting-protobuf@0.3.1:
dependencies:
protobufjs "^6.8.6"
apollo-engine-reporting-protobuf@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.0.tgz#e34c192d86493b33a73181fd6be75721559111ec"
integrity sha512-cXHZSienkis8v4RhqB3YG3DkaksqLpcxApRLTpRMs7IXNozgV7CUPYGFyFBEra1ZFgUyHXx4G9MpelV+n2cCfA==
dependencies:
protobufjs "^6.8.6"
apollo-engine-reporting@1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.6.tgz#579ba2da85ff848bd92be1b0f1ad61f0c57e3585"
@ -1396,6 +1419,18 @@ apollo-engine-reporting@1.3.6:
async-retry "^1.2.1"
graphql-extensions "0.7.7"
apollo-engine-reporting@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.0.tgz#3a9bd011b271593e16d7057044898d0a817b197d"
integrity sha512-NMiO3h1cuEBt6QZNGHxivwuyZQnoU/2MMx0gUA8Gyy1ERBhK6P235qoMnvoi34rLmqJuyGPX6tXcab8MpMIzYQ==
dependencies:
apollo-engine-reporting-protobuf "0.4.0"
apollo-graphql "^0.3.3"
apollo-server-env "2.4.0"
apollo-server-types "0.2.0"
async-retry "^1.2.1"
graphql-extensions "0.8.0"
apollo-env@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.5.1.tgz#b9b0195c16feadf0fe9fd5563edb0b9b7d9e97d3"
@ -1464,6 +1499,13 @@ apollo-server-caching@0.4.0:
dependencies:
lru-cache "^5.0.0"
apollo-server-caching@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.0.tgz#446a37ce2d4e24c81833e276638330a634f7bd46"
integrity sha512-l7ieNCGxUaUAVAAp600HjbUJxVaxjJygtPV0tPTe1Q3HkPy6LEWoY6mNHV7T268g1hxtPTxcdRu7WLsJrg7ufw==
dependencies:
lru-cache "^5.0.0"
apollo-server-core@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.9.tgz#75542ad206782e5c31a023b54962e9fdc6404a91"
@ -1490,6 +1532,34 @@ apollo-server-core@2.6.9:
subscriptions-transport-ws "^0.9.11"
ws "^6.0.0"
apollo-server-core@2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.7.0.tgz#c444347dea11149b5b453890506e43dc7e711257"
integrity sha512-CXjXAkgcMBCJZpsZgfAY5W7f5thdxUhn75UgzeH28RTUZ2aKi/LjoCixPWRSF1lU4vuEWneAnM8Vg/KCD+29lQ==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
"@apollographql/graphql-playground-html" "1.6.24"
"@types/ws" "^6.0.0"
apollo-cache-control "0.8.0"
apollo-datasource "0.6.0"
apollo-engine-reporting "1.4.0"
apollo-engine-reporting-protobuf "0.4.0"
apollo-server-caching "0.5.0"
apollo-server-env "2.4.0"
apollo-server-errors "2.3.1"
apollo-server-plugin-base "0.6.0"
apollo-server-types "0.2.0"
apollo-tracing "0.8.0"
fast-json-stable-stringify "^2.0.0"
graphql-extensions "0.8.0"
graphql-subscriptions "^1.0.0"
graphql-tag "^2.9.2"
graphql-tools "^4.0.0"
graphql-upload "^8.0.2"
sha.js "^2.4.11"
subscriptions-transport-ws "^0.9.11"
ws "^6.0.0"
apollo-server-env@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-2.4.0.tgz#6611556c6b627a1636eed31317d4f7ea30705872"
@ -1526,12 +1596,28 @@ apollo-server-plugin-base@0.5.8:
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.8.tgz#77b4127aff4e3514a9d49e3cc61256aee4d9422e"
integrity sha512-ICbaXr0ycQZL5llbtZhg8zyHbxuZ4khdAJsJgiZaUXXP6+F47XfDQ5uwnl/4Sq9fvkpwS0ctvfZ1D+Ks4NvUzA==
apollo-server-testing@~2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.9.tgz#6c1d20a89c0676bf32714405d729c302d62adfb1"
integrity sha512-MQfXAjNsI63O9sY60tQnGy102sqJSr++Yzm+IR44WrK3Z7FHUDisoh6UATly04EDGtO034xtqukzdUNQCK7+rw==
apollo-server-plugin-base@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.0.tgz#4186296ea5d52cfe613961d252a8a2f9e13e6ba6"
integrity sha512-BjfyWpHyKwHOe819gk3wEFwbnVp9Xvos03lkkYTTcXS/8G7xO78aUcE65mmyAC56/ZQ0aodNFkFrhwNtWBQWUQ==
dependencies:
apollo-server-core "2.6.9"
apollo-server-types "0.2.0"
apollo-server-testing@~2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.7.0.tgz#df8f8fb0df85f9781b6822fc92ee36fdc039b024"
integrity sha512-geBTK5T8mqZ2UOscC3oHQ/QoKMINLK+Mmq5ZfZe9UhmdXi+WqQ3/6B+3HMoQ7eQ7D6bNILe8b7N2EKuyUkePdg==
dependencies:
apollo-server-core "2.7.0"
apollo-server-types@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.2.0.tgz#270d7298f709fd8237ebfa48753249e5286df5f2"
integrity sha512-5dgiyXsM90vnfmdXO1ixHvsLn0d9NP4tWufmr3ZmjKv00r4JAQNUaUdgOSGbRIKoHELQGwxUuTySTZ/tYfGaNQ==
dependencies:
apollo-engine-reporting-protobuf "0.4.0"
apollo-server-caching "0.5.0"
apollo-server-env "2.4.0"
apollo-server@~2.6.9:
version "2.6.9"
@ -1552,6 +1638,14 @@ apollo-tracing@0.7.4:
apollo-server-env "2.4.0"
graphql-extensions "0.7.7"
apollo-tracing@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.0.tgz#28cd9c61a4db12b2c24dad67fdedd309806c1650"
integrity sha512-cNOtOlyZ56iJRsCjnxjM1V0SnQ2ZZttuyoeOejdat6llPfk5bfYTVOKMjdbSfDvU33LS9g9sqNJCT0MwrEPFKQ==
dependencies:
apollo-server-env "2.4.0"
graphql-extensions "0.8.0"
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"
@ -2989,10 +3083,10 @@ eslint-plugin-import@~2.18.0:
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-jest@~22.8.0:
version "22.8.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.8.0.tgz#242ef5459e8da25d2c41438e95eb546e03d7fae1"
integrity sha512-2VftZMfILmlhL3VMq5ptHRIuyyXb3ShDEDb1J1UjvWNzm4l+UK/YmwNuTuJcM0gv8pJuOfiR/8ZptJ8Ou68pFw==
eslint-plugin-jest@~22.9.0:
version "22.9.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.9.0.tgz#2573dbcb4f1066b96a6e6d3b9aa439c80b28975a"
integrity sha512-V89BUiwf76FHlhj1mlNhNyvpzTy8VbWCh2RZpKYz/XDSl/pcuwFiE/LMt7r3q1sRKygzEMjbYeDob8MMuvakXg==
eslint-plugin-node@~9.1.0:
version "9.1.0"
@ -3649,6 +3743,15 @@ graphql-extensions@0.7.7:
"@apollographql/apollo-tools" "^0.3.6"
apollo-server-env "2.4.0"
graphql-extensions@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.8.0.tgz#b3fe7915aa84eef5a39135840840cc4d2e700c46"
integrity sha512-zV9RefkusIXqi9ZJtl7IJ5ecjDKdb7PLAb5E3CmxX3OK1GwNCIubp0vE7Fp4fXlCUKgTB1Woubs0zj71JT8o0A==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
apollo-server-env "2.4.0"
apollo-server-types "0.2.0"
graphql-iso-date@~3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz#bd2d0dc886e0f954cbbbc496bbf1d480b57ffa96"

View File

@ -24,7 +24,7 @@
"cross-env": "^5.2.0",
"cypress": "^3.4.0",
"cypress-cucumber-preprocessor": "^1.12.0",
"cypress-file-upload": "^3.3.1",
"cypress-file-upload": "^3.3.2",
"cypress-plugin-retries": "^1.2.2",
"dotenv": "^8.0.0",
"faker": "Marak/faker.js#master",

View File

@ -23,49 +23,61 @@
:modalsData="menuModalsData"
style="float-right"
:is-owner="isAuthor(author.id)"
@showEditCommentMenu="editCommentMenu"
/>
</no-ssr>
<!-- eslint-disable vue/no-v-html -->
<!-- TODO: replace editor content with tiptap render view -->
<div v-if="isCollapsed" v-html="comment.contentExcerpt" style="padding-left: 40px;" />
<div
v-show="comment.content !== comment.contentExcerpt"
style="text-align: right; margin-right: 20px; margin-top: -12px;"
>
<a v-if="isCollapsed" style="padding-left: 40px;" @click="isCollapsed = !isCollapsed">
{{ $t('comment.show.more') }}
</a>
<ds-space margin-bottom="small" />
<div v-if="openEditCommentMenu">
<hc-edit-comment-form
:comment="comment"
:post="post"
@showEditCommentMenu="editCommentMenu"
/>
</div>
<div v-if="!isCollapsed" v-html="comment.content" style="padding-left: 40px;" />
<div style="text-align: right; margin-right: 20px; margin-top: -12px;">
<a v-if="!isCollapsed" @click="isCollapsed = !isCollapsed" style="padding-left: 40px; ">
{{ $t('comment.show.less') }}
</a>
<div v-show="!openEditCommentMenu">
<div v-if="isCollapsed" v-html="comment.contentExcerpt" style="padding-left: 40px;" />
<div
v-show="comment.content !== comment.contentExcerpt"
style="text-align: right; margin-right: 20px; margin-top: -12px;"
>
<a v-if="isCollapsed" style="padding-left: 40px;" @click="isCollapsed = !isCollapsed">
{{ $t('comment.show.more') }}
</a>
</div>
<div v-if="!isCollapsed" v-html="comment.content" style="padding-left: 40px;" />
<div style="text-align: right; margin-right: 20px; margin-top: -12px;">
<a v-if="!isCollapsed" @click="isCollapsed = !isCollapsed" style="padding-left: 40px; ">
{{ $t('comment.show.less') }}
</a>
</div>
</div>
<ds-space margin-bottom="small" />
</ds-card>
</div>
</template>
<!-- eslint-enable vue/no-v-html -->
<script>
import gql from 'graphql-tag'
import { mapGetters } from 'vuex'
import { mapGetters, mapMutations } from 'vuex'
import HcUser from '~/components/User'
import ContentMenu from '~/components/ContentMenu'
import HcEditCommentForm from '~/components/comments/EditCommentForm/EditCommentForm'
export default {
data: function() {
return {
isCollapsed: true,
openEditCommentMenu: false,
}
},
components: {
HcUser,
ContentMenu,
HcEditCommentForm,
},
props: {
post: { type: Object, default: () => {} },
comment: {
type: Object,
default() {
@ -112,9 +124,16 @@ export default {
},
},
methods: {
...mapMutations({
setEditPending: 'editor/SET_EDIT_PENDING',
}),
isAuthor(id) {
return this.user.id === id
},
editCommentMenu(showMenu) {
this.openEditCommentMenu = showMenu
this.setEditPending(showMenu)
},
async deleteCommentCallback() {
try {
var gqlMutation = gql`

View File

@ -76,14 +76,13 @@ export default {
}
if (this.isOwner && this.resourceType === 'comment') {
// routes.push({
// name: this.$t(`comment.menu.edit`),
// callback: () => {
// /* eslint-disable-next-line no-console */
// console.log('EDIT COMMENT')
// },
// icon: 'edit'
// })
routes.push({
name: this.$t(`comment.menu.edit`),
callback: () => {
this.$emit('showEditCommentMenu', true)
},
icon: 'edit',
})
routes.push({
name: this.$t(`comment.menu.delete`),
callback: () => {

View File

@ -1,5 +1,5 @@
<template>
<ds-form v-model="form" @submit="handleSubmit">
<ds-form v-show="!editPending" v-model="form" @submit="handleSubmit">
<template slot-scope="{ errors }">
<ds-card>
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
@ -27,6 +27,7 @@ import gql from 'graphql-tag'
import HcEditor from '~/components/Editor/Editor'
import PostCommentsQuery from '~/graphql/PostCommentsQuery.js'
import CommentMutations from '~/graphql/CommentMutations.js'
import { mapGetters } from 'vuex'
export default {
components: {
@ -46,6 +47,11 @@ export default {
users: [],
}
},
computed: {
...mapGetters({
editPending: 'editor/editPending',
}),
},
methods: {
updateEditorContent(value) {
const content = value.replace(/<(?:.|\n)*?>/gm, '').trim()

View File

@ -40,6 +40,7 @@ describe('CommentForm.vue', () => {
'editor/placeholder': () => {
return 'some cool placeholder'
},
'editor/editPending': () => false,
}
const store = new Vuex.Store({
getters,

View File

@ -16,11 +16,12 @@
</span>
</h3>
<ds-space margin-bottom="large" />
<div v-if="comments && comments.length" class="comments">
<div v-if="comments && comments.length" id="comments" class="comments">
<comment
v-for="(comment, index) in comments"
:key="comment.id"
:comment="comment"
:post="post"
@deleteComment="comments.splice(index, 1)"
/>
</div>

View File

@ -0,0 +1,108 @@
<template>
<ds-form v-model="form" @submit="handleSubmit">
<template slot-scope="{ errors }">
<ds-card>
<!-- with no-ssr the content is not shown -->
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
<ds-space />
<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: '40%', md: '20%', sm: '30%', xs: '30%' }">
<ds-button ghost class="cancelBtn" @click.prevent="closeEditWindow">
{{ $t('actions.cancel') }}
</ds-button>
</ds-flex-item>
<ds-flex-item :width="{ base: '40%', md: '20%', sm: '40%', xs: '40%' }">
<ds-button type="submit" :loading="loading" :disabled="disabled || errors" primary>
{{ $t('post.comment.submit') }}
</ds-button>
</ds-flex-item>
</ds-flex>
</ds-card>
</template>
</ds-form>
</template>
<script>
import gql from 'graphql-tag'
import HcEditor from '~/components/Editor/Editor'
import { mapMutations } from 'vuex'
import CommentMutations from '~/graphql/CommentMutations.js'
export default {
components: {
HcEditor,
},
props: {
comment: {
type: Object,
default() {
return {}
},
},
},
data() {
return {
disabled: true,
loading: false,
form: {
content: this.comment.content,
},
users: [],
}
},
methods: {
...mapMutations({
setEditPending: 'editor/SET_EDIT_PENDING',
}),
updateEditorContent(value) {
const sanitizedContent = value.replace(/<(?:.|\n)*?>/gm, '').trim()
this.disabled = value === this.comment.content || sanitizedContent.length < 1
this.form.content = value
},
closeEditWindow() {
this.$emit('showEditCommentMenu', false)
},
handleSubmit() {
this.loading = true
this.disabled = true
this.$apollo
.mutate({
mutation: CommentMutations().UpdateComment,
variables: {
content: this.form.content,
id: this.comment.id,
},
})
.then(() => {
this.loading = false
this.$toast.success(this.$t('post.comment.updated'))
this.disabled = false
this.$emit('showEditCommentMenu', false)
this.setEditPending(false)
})
.catch(err => {
this.$toast.error(err.message)
})
},
},
apollo: {
User: {
query() {
return gql`
{
User(orderBy: slug_asc) {
id
slug
}
}
`
},
result({ data: { User } }) {
this.users = User
},
},
},
}
</script>

View File

@ -20,5 +20,14 @@ export default () => {
}
}
`,
UpdateComment: gql`
mutation($content: String!, $id: ID!) {
UpdateComment(content: $content, id: $id) {
id
content
contentExcerpt
}
}
`,
}
}

View File

@ -2,7 +2,7 @@ import gql from 'graphql-tag'
export default app => {
const lang = app.$i18n.locale().toUpperCase()
return gql(`
return gql`
query Comment($postId: ID) {
Comment(postId: $postId) {
id
@ -30,5 +30,5 @@ export default app => {
}
}
}
`)
`
}

View File

@ -2,7 +2,7 @@ import gql from 'graphql-tag'
export default i18n => {
const lang = i18n.locale().toUpperCase()
return gql(`
return gql`
query Post($slug: String!) {
Post(slug: $slug) {
id
@ -73,7 +73,7 @@ export default i18n => {
shoutedByCurrentUser
}
}
`)
`
}
export const filterPosts = i18n => {
@ -118,5 +118,5 @@ export const filterPosts = i18n => {
shoutedCount
}
}
`)
`
}

View File

@ -252,7 +252,8 @@
},
"comment": {
"submit": "Comment",
"submitted": "Comment Submitted"
"submitted": "Comment Submitted",
"updated": "Changes Saved"
}
},
"comment": {

View File

@ -95,7 +95,7 @@
"eslint-config-standard": "~12.0.0",
"eslint-loader": "~2.2.1",
"eslint-plugin-import": "~2.18.0",
"eslint-plugin-jest": "~22.8.0",
"eslint-plugin-jest": "~22.9.0",
"eslint-plugin-node": "~9.1.0",
"eslint-plugin-prettier": "~3.1.0",
"eslint-plugin-promise": "~4.2.1",

View File

@ -1,6 +1,7 @@
export const state = () => {
return {
placeholder: null,
editPending: false,
}
}
@ -8,10 +9,16 @@ export const getters = {
placeholder(state) {
return state.placeholder
},
editPending(state) {
return state.editPending
},
}
export const mutations = {
SET_PLACEHOLDER_TEXT(state, text) {
state.placeholder = text
},
SET_EDIT_PENDING(state, boolean) {
state.editPending = boolean
},
}

View File

@ -4320,10 +4320,10 @@ eslint-plugin-import@~2.18.0:
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-jest@~22.8.0:
version "22.8.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.8.0.tgz#242ef5459e8da25d2c41438e95eb546e03d7fae1"
integrity sha512-2VftZMfILmlhL3VMq5ptHRIuyyXb3ShDEDb1J1UjvWNzm4l+UK/YmwNuTuJcM0gv8pJuOfiR/8ZptJ8Ou68pFw==
eslint-plugin-jest@~22.9.0:
version "22.9.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.9.0.tgz#2573dbcb4f1066b96a6e6d3b9aa439c80b28975a"
integrity sha512-V89BUiwf76FHlhj1mlNhNyvpzTy8VbWCh2RZpKYz/XDSl/pcuwFiE/LMt7r3q1sRKygzEMjbYeDob8MMuvakXg==
eslint-plugin-node@~9.1.0:
version "9.1.0"

View File

@ -1827,10 +1827,10 @@ cypress-cucumber-preprocessor@^1.12.0:
glob "^7.1.2"
through "^2.3.8"
cypress-file-upload@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.3.1.tgz#19bb6c296ffc492dbfae8a7511c94d6b4d0ad4d5"
integrity sha512-iUtq/a30i73JXx9sUj5HhmuEV9pHMV2/7C06H8/zFDSgFweFSwKL0SSprQu8Ewf7cAEsExBKigwlLQYFdTW8PA==
cypress-file-upload@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.3.2.tgz#d9bb00cac756fd4b5f68079f19745fe40d97fdd4"
integrity sha512-39CW6/rTmn7AGa+ZrD37R+ANnnWAesbUj2RpBfbpRzV+KTAlF+m9Djbd2f325w0JGY0A2eO0w6TYYo+NvBTnoA==
cypress-plugin-retries@^1.2.2:
version "1.2.2"