diff --git a/package-lock.json b/package-lock.json index c4ffb60fb..359d2bdeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19555,9 +19555,9 @@ "dev": true }, "popper.js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz", - "integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=", + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", + "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==", "dev": true }, "portfinder": { @@ -19573,7 +19573,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -23268,23 +23268,38 @@ "dev": true }, "react-popper": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.0.0.tgz", - "integrity": "sha1-uZRSFE6P5KzHf6PZWajHngemUIQ=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.2.tgz", + "integrity": "sha512-UbFWj55Yt9uqvy0oZ+vULDL2Bw1oxeZF9/JzGyxQ5ypgauRH/XlarA5+HLZWro/Zss6Ht2kqpegtb6sYL8GUGw==", "dev": true, "requires": { - "babel-runtime": "6.x.x", - "create-react-context": "^0.2.1", - "popper.js": "^1.14.1", + "@babel/runtime": "^7.1.2", + "create-react-context": "<=0.2.2", + "popper.js": "^1.14.4", "prop-types": "^15.6.1", - "typed-styles": "^0.0.5", - "warning": "^3.0.0" + "typed-styles": "^0.0.7", + "warning": "^4.0.2" }, "dependencies": { + "@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true + }, "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", + "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -27160,9 +27175,9 @@ } }, "typed-styles": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.5.tgz", - "integrity": "sha512-ht+rEe5UsdEBAa3gr64+QjUOqjOLJfWLvl5HZR5Ev9uo/OnD3p43wPeFSB1hNFc13GXQF/JU1Bn0YHLUqBRIlw==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==", "dev": true }, "typedarray": { diff --git a/package.json b/package.json index 4ea9fb7cc..1727e4375 100644 --- a/package.json +++ b/package.json @@ -256,7 +256,7 @@ "react-dev-utils": "6.0.0-next.3e165448", "react-dom": "^16.5.2", "react-final-form": "^3.6.4", - "react-popper": "^1.0.0", + "react-popper": "^1.3.2", "react-relay": "^1.7.0-rc.1", "react-responsive": "^5.0.0", "react-test-renderer": "^16.5.2", diff --git a/src/core/client/admin/components/DecisionHistoryButton.css b/src/core/client/admin/components/DecisionHistoryButton.css index 001a0a329..fce678540 100644 --- a/src/core/client/admin/components/DecisionHistoryButton.css +++ b/src/core/client/admin/components/DecisionHistoryButton.css @@ -1,5 +1,4 @@ .popover { - z-index: var(--zindex-popover); width: 350px; } diff --git a/src/core/client/framework/lib/messages.tsx b/src/core/client/framework/lib/messages.tsx index 861864643..5c5f11776 100644 --- a/src/core/client/framework/lib/messages.tsx +++ b/src/core/client/framework/lib/messages.tsx @@ -13,8 +13,14 @@ export const VALIDATION_REQUIRED = () => ( ); export const VALIDATION_TOO_SHORT = (minLength: number) => ( - - {"This field must contain at least {$minLength} characters."} + + {"Please enter at least {$minLength} characters."} + +); + +export const VALIDATION_TOO_LONG = (maxLength: number) => ( + + {"Please enter at max {$maxLength} characters."} ); diff --git a/src/core/client/framework/lib/validation.tsx b/src/core/client/framework/lib/validation.tsx index efbe738c3..0e53ca9eb 100644 --- a/src/core/client/framework/lib/validation.tsx +++ b/src/core/client/framework/lib/validation.tsx @@ -19,6 +19,8 @@ import { USERNAME_TOO_LONG, USERNAME_TOO_SHORT, VALIDATION_REQUIRED, + VALIDATION_TOO_LONG, + VALIDATION_TOO_SHORT, } from "./messages"; export type Validator = (v: T, values: V) => ReactNode; @@ -76,6 +78,24 @@ export const validateURL = createValidator( INVALID_URL() ); +/** + * validateMinLength is a Validator that checks that the field has a min length of characters + */ +export const validateMinLength = (minLength: number) => + createValidator( + v => !v || v.length >= minLength, + VALIDATION_TOO_SHORT(minLength) + ); + +/** + * validateMaxLength is a Validator that checks that the field has max length of characters + */ +export const validateMaxLength = (maxLength: number) => + createValidator( + v => !v || v.length <= maxLength, + VALIDATION_TOO_LONG(maxLength) + ); + /** * validateUsernameMinLength is a Validator that checks that the username has a min length of characters */ diff --git a/src/core/client/framework/testHelpers/byID.ts b/src/core/client/framework/testHelpers/byID.ts new file mode 100644 index 000000000..82888c57d --- /dev/null +++ b/src/core/client/framework/testHelpers/byID.ts @@ -0,0 +1,36 @@ +import { ReactTestInstance } from "react-test-renderer"; + +import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText"; + +const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( + i: ReactTestInstance +) => { + // Only look at dom components. + if (typeof i.type !== "string" || !i.props.id) { + return false; + } + return matchText(pattern, i.props.id, { + collapseWhitespace: false, + ...options, + }); +}; + +export function getByID( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.find(matcher(pattern, options)); +} + +export function queryByID( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + const results = container.findAll(matcher(pattern, options)); + if (!results.length) { + return null; + } + return results[0]; +} diff --git a/src/core/client/framework/testHelpers/byLabelText.ts b/src/core/client/framework/testHelpers/byLabelText.ts index ed69f90c8..19b589b04 100644 --- a/src/core/client/framework/testHelpers/byLabelText.ts +++ b/src/core/client/framework/testHelpers/byLabelText.ts @@ -62,7 +62,7 @@ export function queryAllByLabelText( options?: TextMatchOptions ) { const matches = container.findAll(ariaLabelMatcher(pattern, options)); - queryAllByText(container, pattern).forEach(i => { + queryAllByText(container, pattern, options).forEach(i => { if (typeof i.type !== "string") { return; } diff --git a/src/core/client/framework/testHelpers/byText.ts b/src/core/client/framework/testHelpers/byText.ts index 104aacbd9..60e3f96db 100644 --- a/src/core/client/framework/testHelpers/byText.ts +++ b/src/core/client/framework/testHelpers/byText.ts @@ -1,6 +1,8 @@ import React from "react"; import { ReactTestInstance } from "react-test-renderer"; +import findParentsWithType from "./findParentsWithType"; +import findParentWithType from "./findParentWithType"; import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText"; const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( @@ -10,6 +12,12 @@ const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( if (typeof i.type !== "string") { return false; } + if ( + i.props.dangerouslySetInnerHTML && + matchText(pattern, i.props.dangerouslySetInnerHTML.__html, options) + ) { + return true; + } if (!i.props.children) { return false; } @@ -22,20 +30,34 @@ const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( return false; }; +interface SelectorOptions { + selector?: string | React.ComponentClass | React.StatelessComponent; +} + export function getByText( container: ReactTestInstance, pattern: TextMatchPattern, - options?: TextMatchOptions + options?: TextMatchOptions & SelectorOptions ) { - return container.find(matcher(pattern, options)); + const result = findParentWithType( + container.find(matcher(pattern, options)), + options && options.selector + ); + if (!result) { + throw new Error(`Couldn't find text ${pattern}`); + } + return result; } export function getAllByText( container: ReactTestInstance, pattern: TextMatchPattern, - options?: TextMatchOptions + options?: TextMatchOptions & SelectorOptions ) { - const results = container.findAll(matcher(pattern, options)); + const results = findParentsWithType( + container.findAll(matcher(pattern, options)), + options && options.selector + ); if (!results.length) { throw new Error(`Couldn't find text ${pattern}`); } @@ -45,9 +67,12 @@ export function getAllByText( export function queryByText( container: ReactTestInstance, pattern: TextMatchPattern, - options?: TextMatchOptions + options?: TextMatchOptions & SelectorOptions ) { - const results = container.findAll(matcher(pattern, options)); + const results = findParentsWithType( + container.findAll(matcher(pattern, options)), + options && options.selector + ); if (!results.length) { return null; } @@ -57,7 +82,10 @@ export function queryByText( export function queryAllByText( container: ReactTestInstance, pattern: TextMatchPattern, - options?: TextMatchOptions + options?: TextMatchOptions & SelectorOptions ) { - return container.findAll(matcher(pattern, options)); + return findParentsWithType( + container.findAll(matcher(pattern, options)), + options && options.selector + ); } diff --git a/src/core/client/framework/testHelpers/findParentWithType.ts b/src/core/client/framework/testHelpers/findParentWithType.ts new file mode 100644 index 000000000..de842c3b2 --- /dev/null +++ b/src/core/client/framework/testHelpers/findParentWithType.ts @@ -0,0 +1,20 @@ +import { ReactTestInstance } from "react-test-renderer"; + +export default function findParentWithType( + instance: ReactTestInstance, + selector: + | string + | React.ComponentClass + | React.StatelessComponent = "*" +): ReactTestInstance | null { + if (selector === "*") { + return instance; + } + if (instance.type === selector) { + return instance; + } + if (instance.parent) { + return findParentWithType(instance.parent, selector); + } + return null; +} diff --git a/src/core/client/framework/testHelpers/findParentsWithType.ts b/src/core/client/framework/testHelpers/findParentsWithType.ts new file mode 100644 index 000000000..ee280b3e5 --- /dev/null +++ b/src/core/client/framework/testHelpers/findParentsWithType.ts @@ -0,0 +1,15 @@ +import { ReactTestInstance } from "react-test-renderer"; + +import findParentWithType from "./findParentWithType"; + +export default function findParentsWithType( + instances: ReactTestInstance[], + selector: + | string + | React.ComponentClass + | React.StatelessComponent = "*" +): ReactTestInstance[] { + return instances + .map(i => findParentWithType(i, selector)) + .filter(i => i) as ReactTestInstance[]; +} diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts index b8b00057e..bbf6a5107 100644 --- a/src/core/client/framework/testHelpers/index.ts +++ b/src/core/client/framework/testHelpers/index.ts @@ -20,3 +20,5 @@ export { default as matchText } from "./matchText"; export { default as toJSON } from "./toJSON"; export { default as replaceHistoryLocation } from "./replaceHistoryLocation"; export { default as createAuthToken } from "./createAuthToken"; +export { default as findParentsWithType } from "./findParentsWithType"; +export { default as findParentWithType } from "./findParentWithType"; diff --git a/src/core/client/framework/testHelpers/within.ts b/src/core/client/framework/testHelpers/within.ts index 1131eea1a..b9e880ae5 100644 --- a/src/core/client/framework/testHelpers/within.ts +++ b/src/core/client/framework/testHelpers/within.ts @@ -1,5 +1,9 @@ import { ReactTestInstance } from "react-test-renderer"; +import { + getByID, + queryByID, +} from "./byID"; import { getAllByLabelText, getByLabelText, @@ -47,6 +51,8 @@ export default function within(container: ReactTestInstance) { getAllByTestID: applyContainer(container, getAllByTestID), queryByTestID: applyContainer(container, queryByTestID), queryAllByTestID: applyContainer(container, queryAllByTestID), + getByID: applyContainer(container, getByID), + queryByID: applyContainer(container, queryByID), getByText: applyContainer(container, getByText), getAllByText: applyContainer(container, getAllByText), queryByText: applyContainer(container, queryByText), diff --git a/src/core/client/stream/components/App.tsx b/src/core/client/stream/components/App.tsx index fda940372..6ab29fbae 100644 --- a/src/core/client/stream/components/App.tsx +++ b/src/core/client/stream/components/App.tsx @@ -19,10 +19,10 @@ const App: StatelessComponent = props => { - + - + diff --git a/src/core/client/stream/components/__snapshots__/App.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/App.spec.tsx.snap index 8962ad068..3e1c12653 100644 --- a/src/core/client/stream/components/__snapshots__/App.spec.tsx.snap +++ b/src/core/client/stream/components/__snapshots__/App.spec.tsx.snap @@ -10,11 +10,13 @@ exports[`renders correctly 1`] = ` className="App-tabContent" > diff --git a/src/core/client/stream/mutations/CreateCommentDontAgreeMutation.ts b/src/core/client/stream/mutations/CreateCommentDontAgreeMutation.ts new file mode 100644 index 000000000..cb1af6d57 --- /dev/null +++ b/src/core/client/stream/mutations/CreateCommentDontAgreeMutation.ts @@ -0,0 +1,55 @@ +import { pick } from "lodash"; +import { graphql } from "react-relay"; +import { Environment } from "relay-runtime"; + +import { + commitMutationPromiseNormalized, + createMutationContainer, +} from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; + +import { CreateCommentDontAgreeMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentDontAgreeMutation.graphql"; + +export type CreateCommentDontAgreeInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; + +const mutation = graphql` + mutation CreateCommentDontAgreeMutation( + $input: CreateCommentDontAgreeInput! + ) { + createCommentDontAgree(input: $input) { + comment { + id + myActionPresence { + dontAgree + } + } + clientMutationId + } + } +`; + +let clientMutationId = 0; + +function commit(environment: Environment, input: CreateCommentDontAgreeInput) { + return commitMutationPromiseNormalized(environment, { + mutation, + variables: { + input: { + ...pick(input, ["commentID", "commentRevisionID", "additionalDetails"]), + clientMutationId: (clientMutationId++).toString(), + }, + }, + }); +} + +export const withCreateCommentDontAgreeMutation = createMutationContainer( + "createCommentDontAgree", + commit +); + +export type CreateCommentDontAgreeMutation = ( + input: CreateCommentDontAgreeInput +) => Promise; diff --git a/src/core/client/stream/mutations/CreateCommentFlagMutation.ts b/src/core/client/stream/mutations/CreateCommentFlagMutation.ts new file mode 100644 index 000000000..e0e9b88cf --- /dev/null +++ b/src/core/client/stream/mutations/CreateCommentFlagMutation.ts @@ -0,0 +1,58 @@ +import { pick } from "lodash"; +import { graphql } from "react-relay"; +import { Environment } from "relay-runtime"; + +import { + commitMutationPromiseNormalized, + createMutationContainer, +} from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; + +import { CreateCommentFlagMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentFlagMutation.graphql"; + +export type CreateCommentFlagInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; + +const mutation = graphql` + mutation CreateCommentFlagMutation($input: CreateCommentFlagInput!) { + createCommentFlag(input: $input) { + comment { + id + myActionPresence { + flag + } + } + clientMutationId + } + } +`; + +let clientMutationId = 0; + +function commit(environment: Environment, input: CreateCommentFlagInput) { + return commitMutationPromiseNormalized(environment, { + mutation, + variables: { + input: { + ...pick(input, [ + "commentID", + "commentRevisionID", + "reason", + "additionalDetails", + ]), + clientMutationId: (clientMutationId++).toString(), + }, + }, + }); +} + +export const withCreateCommentFlagMutation = createMutationContainer( + "createCommentFlag", + commit +); + +export type CreateCommentFlagMutation = ( + input: CreateCommentFlagInput +) => Promise; diff --git a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts index ef0b7a61a..8290c4fec 100644 --- a/src/core/client/stream/mutations/CreateCommentReactionMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentReactionMutation.ts @@ -1,3 +1,4 @@ +import { pick } from "lodash"; import { graphql } from "react-relay"; import { Environment } from "relay-runtime"; @@ -37,7 +38,7 @@ function commit(environment: Environment, input: CreateCommentReactionInput) { mutation, variables: { input: { - ...input, + ...pick(input, ["commentID", "commentRevisionID"]), clientMutationId: clientMutationId.toString(), }, }, diff --git a/src/core/client/stream/mutations/EditCommentMutation.ts b/src/core/client/stream/mutations/EditCommentMutation.ts index 0b85b6a26..cd59f464c 100644 --- a/src/core/client/stream/mutations/EditCommentMutation.ts +++ b/src/core/client/stream/mutations/EditCommentMutation.ts @@ -1,3 +1,4 @@ +import { pick } from "lodash"; import { graphql } from "react-relay"; import { Environment } from "relay-runtime"; @@ -38,7 +39,7 @@ function commit(environment: Environment, input: EditCommentInput) { mutation, variables: { input: { - ...input, + ...pick(input, ["commentID", "body"]), clientMutationId: clientMutationId.toString(), }, }, diff --git a/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts b/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts index 8534cb20f..1abc3444f 100644 --- a/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts +++ b/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts @@ -1,3 +1,4 @@ +import { pick } from "lodash"; import { graphql } from "react-relay"; import { Environment } from "relay-runtime"; @@ -36,7 +37,7 @@ function commit(environment: Environment, input: RemoveCommentReactionInput) { mutation, variables: { input: { - ...input, + ...pick(input, ["commentID"]), clientMutationId: (clientMutationId++).toString(), }, }, diff --git a/src/core/client/stream/mutations/index.ts b/src/core/client/stream/mutations/index.ts index 0669b0a83..3e2950242 100644 --- a/src/core/client/stream/mutations/index.ts +++ b/src/core/client/stream/mutations/index.ts @@ -3,6 +3,16 @@ export { CreateCommentMutation, CreateCommentInput, } from "./CreateCommentMutation"; +export { + withCreateCommentFlagMutation, + CreateCommentFlagMutation, + CreateCommentFlagInput, +} from "./CreateCommentFlagMutation"; +export { + withCreateCommentDontAgreeMutation, + CreateCommentDontAgreeMutation, + CreateCommentDontAgreeInput, +} from "./CreateCommentDontAgreeMutation"; export { withCreateCommentReplyMutation, CreateCommentReplyMutation, diff --git a/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx b/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx index 11636d01c..fee7a40a0 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/Comment.spec.tsx @@ -7,7 +7,6 @@ import Comment from "./Comment"; it("renders username and body", () => { const props: PropTypesOf = { - id: "comment-id", username: "Marvin", body: "Woof", createdAt: "1995-12-17T03:24:00.000Z", diff --git a/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx index 90c5b0fb7..39955769e 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx @@ -13,7 +13,6 @@ import Username from "./Username"; import styles from "./Comment.css"; export interface CommentProps { - id?: string; className?: string; username: string | null; body: string | null; @@ -31,7 +30,7 @@ const Comment: StatelessComponent = props => { role="article" className={cn(styles.root, { [styles.highlight]: props.highlight })} > - + {props.username && {props.username}} diff --git a/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap index d4ab9ae64..44e0e6251 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/Comment.spec.tsx.snap @@ -7,7 +7,6 @@ exports[`renders username and body 1`] = ` > diff --git a/src/core/client/stream/tabs/comments/components/CommentsPane.tsx b/src/core/client/stream/tabs/comments/components/CommentsPane.tsx index 3bb4bc167..5a1d870e5 100644 --- a/src/core/client/stream/tabs/comments/components/CommentsPane.tsx +++ b/src/core/client/stream/tabs/comments/components/CommentsPane.tsx @@ -1,15 +1,15 @@ import * as React from "react"; import { StatelessComponent } from "react"; -import PermalinkViewQuery from "../queries/PermalinkViewQuery"; import StreamQuery from "../queries/StreamQuery"; +import PermalinkView from "../views/permalink"; export interface CommentsPaneProps { showPermalinkView: boolean; } const CommentsPane: StatelessComponent = props => { - return props.showPermalinkView ? : ; + return props.showPermalinkView ? : ; }; export default CommentsPane; diff --git a/src/core/client/stream/tabs/comments/components/EditCommentForm.tsx b/src/core/client/stream/tabs/comments/components/EditCommentForm.tsx index cedf6846e..716ace121 100644 --- a/src/core/client/stream/tabs/comments/components/EditCommentForm.tsx +++ b/src/core/client/stream/tabs/comments/components/EditCommentForm.tsx @@ -56,7 +56,6 @@ const EditCommentForm: StatelessComponent = props => { className={props.className} autoComplete="off" onSubmit={handleSubmit} - id={`comments-editCommentForm-form-${props.id}`} >
@@ -120,7 +119,6 @@ const EditCommentForm: StatelessComponent = props => { {props.expired ? ( + ); + } +} + +export default ReportButton; diff --git a/src/core/client/stream/tabs/comments/components/ReportButton/ReportButtonWithPopover.tsx b/src/core/client/stream/tabs/comments/components/ReportButton/ReportButtonWithPopover.tsx new file mode 100644 index 000000000..03023bee9 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ReportButton/ReportButtonWithPopover.tsx @@ -0,0 +1,65 @@ +import React from "react"; + +import { oncePerFrame } from "talk-common/utils"; +import { ClickOutside, Popover } from "talk-ui/components"; + +import ReportCommentView from "../../views/reportComment"; +import ReportButton from "./ReportButton"; + +import { PropTypesOf } from "talk-ui/types"; + +interface Props { + comment: { id: string } & PropTypesOf["comment"]; + reported: boolean; +} + +class ReportButtonWithPopover extends React.Component { + // Helper that prevents calling toggleVisibility more then once per frame. + // In essence this means we'll process an event only once. + // This might happen, when clicking on the button which will + // cause its onClick to happen as well as onClickOutside. + private toggleVisibilityOncePerFrame = oncePerFrame( + (toggleVisibility: () => void) => toggleVisibility() + ); + + public render() { + const { comment, reported } = this.props; + const popoverID = `report-popover-${comment.id}`; + return ( + ( + + this.toggleVisibilityOncePerFrame(toggleVisibility) + } + > + + this.toggleVisibilityOncePerFrame(toggleVisibility) + } + onResize={scheduleUpdate} + /> + + )} + > + {({ toggleVisibility, forwardRef, visible }) => ( + + !reported && this.toggleVisibilityOncePerFrame(toggleVisibility) + } + aria-controls={popoverID} + forwardRef={forwardRef} + active={visible} + reported={reported} + /> + )} + + ); + } +} + +export default ReportButtonWithPopover; diff --git a/src/core/client/stream/tabs/comments/components/ReportButton/__snapshots__/ReportButton.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/ReportButton/__snapshots__/ReportButton.spec.tsx.snap new file mode 100644 index 000000000..586801c83 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ReportButton/__snapshots__/ReportButton.spec.tsx.snap @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders report button 1`] = ` + + + + flag + + + + + Report + + + +`; + +exports[`renders reported button 1`] = ` + + + + flag + + + + + Reported + + + +`; diff --git a/src/core/client/stream/tabs/comments/components/ReportButton/index.ts b/src/core/client/stream/tabs/comments/components/ReportButton/index.ts new file mode 100644 index 000000000..a96556e6f --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ReportButton/index.ts @@ -0,0 +1,2 @@ +export { default as ReportButton } from "./ReportButton"; +export { default as ReportButtonWithPopover } from "./ReportButtonWithPopover"; diff --git a/src/core/client/stream/tabs/comments/components/Stream.tsx b/src/core/client/stream/tabs/comments/components/Stream.tsx index 41bad883a..ff36ae2a5 100644 --- a/src/core/client/stream/tabs/comments/components/Stream.tsx +++ b/src/core/client/stream/tabs/comments/components/Stream.tsx @@ -49,6 +49,7 @@ const Stream: StatelessComponent = props => { diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap index 666ab4bd9..7023d3c75 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/ReplyList.spec.tsx.snap @@ -2,6 +2,7 @@ exports[`renders correctly 1`] = ` @@ -71,6 +72,7 @@ exports[`renders correctly 1`] = ` exports[`when there is more disables load more button 1`] = ` @@ -154,6 +156,7 @@ exports[`when there is more disables load more button 1`] = ` exports[`when there is more renders a load more button 1`] = ` diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/Stream.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/Stream.spec.tsx.snap index fb1b583fc..6e7d3b2cc 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/Stream.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/Stream.spec.tsx.snap @@ -23,6 +23,7 @@ exports[`renders correctly 1`] = ` @@ -149,6 +150,7 @@ exports[`when there is more disables load more button 1`] = ` @@ -289,6 +291,7 @@ exports[`when there is more renders a load more button 1`] = ` @@ -431,6 +434,7 @@ exports[`when use is logged in renders correctly 1`] = ` diff --git a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx index c11c05551..5d717b651 100644 --- a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx @@ -17,7 +17,7 @@ import { withShowAuthPopupMutation, } from "talk-stream/mutations"; -import { Button, HorizontalGutter } from "talk-ui/components"; +import { Button, Flex, HorizontalGutter } from "talk-ui/components"; import ReactionButtonContainer from "./ReactionButtonContainer"; import Comment, { @@ -28,6 +28,7 @@ import ReplyButton from "../components/Comment/ReplyButton"; import EditCommentFormContainer from "./EditCommentFormContainer"; import PermalinkButtonContainer from "./PermalinkButtonContainer"; import ReplyCommentFormContainer from "./ReplyCommentFormContainer"; +import ReportButtonContainer from "./ReportButtonContainer"; interface InnerProps { me: MeData | null; @@ -148,87 +149,96 @@ export class CommentContainer extends Component { const { showReplyDialog, showEditDialog, editable } = this.state; if (showEditDialog) { return ( - +
+ +
); } return ( - - - -
- )) || - undefined - } - footer={ - <> - - {!disableReplies && ( - + + + + + )) || + undefined + } + footer={ + <> + + + {!disableReplies && ( + + )} + + + + + + + + {showConversationLink && ( + )} - - - - {showConversationLink && ( - - )} - - } - /> - {showReplyDialog && ( - + } /> - )} - + {showReplyDialog && ( + + )} + +
); } } @@ -240,6 +250,7 @@ const enhanced = withSetCommentIDMutation( fragment CommentContainer_me on User { id ...ReactionButtonContainer_me + ...ReportButtonContainer_me } `, story: graphql` @@ -271,6 +282,7 @@ const enhanced = withSetCommentIDMutation( ...ReplyCommentFormContainer_comment ...EditCommentFormContainer_comment ...ReactionButtonContainer_comment + ...ReportButtonContainer_comment } `, settings: graphql` diff --git a/src/core/client/stream/tabs/comments/containers/ReportButtonContainer.tsx b/src/core/client/stream/tabs/comments/containers/ReportButtonContainer.tsx new file mode 100644 index 000000000..80fff646c --- /dev/null +++ b/src/core/client/stream/tabs/comments/containers/ReportButtonContainer.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import { graphql } from "react-relay"; + +import { withFragmentContainer } from "talk-framework/lib/relay"; +import { ReportButtonContainer_comment as CommentData } from "talk-stream/__generated__/ReportButtonContainer_comment.graphql"; +import { ReportButtonContainer_me as MeData } from "talk-stream/__generated__/ReportButtonContainer_me.graphql"; + +import { + ReportButton, + ReportButtonWithPopover, +} from "../components/ReportButton"; + +import { + ShowAuthPopupMutation, + withShowAuthPopupMutation, +} from "talk-stream/mutations"; + +interface ReportButtonContainerProps { + comment: CommentData; + me: MeData | null; + showAuthPopup: ShowAuthPopupMutation; +} + +class ReportButtonContainer extends React.Component< + ReportButtonContainerProps +> { + private get loggedIn() { + return this.props.me; + } + private handleSignIn = () => this.props.showAuthPopup({ view: "SIGN_IN" }); + + public render() { + const reported = + this.props.comment.myActionPresence && + (this.props.comment.myActionPresence.flag || + this.props.comment.myActionPresence.dontAgree); + + return this.loggedIn ? ( + + ) : ( + + ); + } +} + +export default withShowAuthPopupMutation( + withFragmentContainer({ + me: graphql` + fragment ReportButtonContainer_me on User { + id + } + `, + comment: graphql` + fragment ReportButtonContainer_comment on Comment { + ...ReportCommentFormContainer_comment + id + myActionPresence { + dontAgree + flag + } + } + `, + })(ReportButtonContainer) +); diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap index 2e77b0068..f4ca7b285 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/CommentContainer.spec.tsx.snap @@ -1,316 +1,465 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`hide reply button 1`] = ` - - - - - - - - } - id="comment-comment-id" - indentLevel={1} - parentAuthorName={null} - showEditedMarker={false} - username="Marvin" - /> - +
+ + + + + + + + + + + + + } + indentLevel={1} + parentAuthorName={null} + showEditedMarker={false} + username="Marvin" + /> + +
`; exports[`renders body only 1`] = ` - - - - - - - - - } - id="comment-comment-id" - indentLevel={1} - parentAuthorName={null} - showEditedMarker={false} - username={null} - /> - +
+ + + + + + + + + + + + + + } + indentLevel={1} + parentAuthorName={null} + showEditedMarker={false} + username={null} + /> + +
`; exports[`renders in reply to 1`] = ` - - - - - - - - - } - id="comment-comment-id" - indentLevel={1} - parentAuthorName="ParentAuthor" - showEditedMarker={false} - username="Marvin" - /> - +
+ + + + + + + + + + + + + + } + indentLevel={1} + parentAuthorName="ParentAuthor" + showEditedMarker={false} + username="Marvin" + /> + +
`; exports[`renders username and body 1`] = ` - - - - - - - - - } - id="comment-comment-id" - indentLevel={1} - parentAuthorName={null} - showEditedMarker={false} - username="Marvin" - /> - +
+ + + + + + + + + + + + + + } + indentLevel={1} + parentAuthorName={null} + showEditedMarker={false} + username="Marvin" + /> + +
`; exports[`shows conversation link 1`] = ` - - - - + + + + + + + + + + + + + - - - - - - } - id="comment-comment-id" - indentLevel={1} - parentAuthorName={null} - showEditedMarker={false} - username="Marvin" - /> - + + } + indentLevel={1} + parentAuthorName={null} + showEditedMarker={false} + username="Marvin" + /> + + `; diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.css b/src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.css similarity index 100% rename from src/core/client/stream/tabs/comments/components/ConversationThread.css rename to src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.css diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.spec.tsx b/src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.spec.tsx similarity index 100% rename from src/core/client/stream/tabs/comments/components/ConversationThread.spec.tsx rename to src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.spec.tsx diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.tsx b/src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.tsx similarity index 87% rename from src/core/client/stream/tabs/comments/components/ConversationThread.tsx rename to src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.tsx index a5af01a8f..157dd65f5 100644 --- a/src/core/client/stream/tabs/comments/components/ConversationThread.tsx +++ b/src/core/client/stream/tabs/comments/views/permalink/components/ConversationThread.tsx @@ -5,12 +5,12 @@ import React, { StatelessComponent } from "react"; import { PropTypesOf } from "talk-framework/types"; import { Button, Counter, Flex, HorizontalGutter } from "talk-ui/components"; -import CommentContainer from "../containers/CommentContainer"; -import LocalReplyListContainer from "../containers/LocalReplyListContainer"; -import { RootParent } from "./Comment"; -import { Circle, Line } from "./Timeline"; +import { RootParent } from "talk-stream/tabs/comments/components/Comment"; +import CommentContainer from "talk-stream/tabs/comments/containers/CommentContainer"; +import LocalReplyListContainer from "talk-stream/tabs/comments/containers/LocalReplyListContainer"; import styles from "./ConversationThread.css"; +import { Circle, Line } from "./Timeline"; export interface ConversationThreadProps { className?: string; @@ -38,9 +38,13 @@ export interface ConversationThreadProps { const ConversationThread: StatelessComponent< ConversationThreadProps > = props => { + const dataTestID = "comments-permalinkView-conversationThread"; if (props.remaining === 0 && props.parents.length === 0) { return ( -
+
+
}> {props.rootParent && ( diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.css b/src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.css similarity index 100% rename from src/core/client/stream/tabs/comments/components/PermalinkView.css rename to src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.css diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.spec.tsx b/src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.spec.tsx similarity index 100% rename from src/core/client/stream/tabs/comments/components/PermalinkView.spec.tsx rename to src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.spec.tsx diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx b/src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.tsx similarity index 93% rename from src/core/client/stream/tabs/comments/components/PermalinkView.tsx rename to src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.tsx index 5f5ed608b..99793b10c 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx +++ b/src/core/client/stream/tabs/comments/views/permalink/components/PermalinkView.tsx @@ -4,9 +4,10 @@ import React, { MouseEvent, StatelessComponent } from "react"; import { PropTypesOf } from "talk-framework/types"; import { Button, Flex, HorizontalGutter, Typography } from "talk-ui/components"; -import UserBoxContainer from "../../../containers/UserBoxContainer"; +import UserBoxContainer from "talk-stream/containers/UserBoxContainer"; +import ReplyListContainer from "talk-stream/tabs/comments/containers/ReplyListContainer"; + import ConversationThreadContainer from "../containers/ConversationThreadContainer"; -import ReplyListContainer from "../containers/ReplyListContainer"; import styles from "./PermalinkView.css"; @@ -50,7 +51,6 @@ const PermalinkView: StatelessComponent = ({ {showAllCommentsHref && (