diff --git a/src/core/client/framework/testHelpers/denormalize.ts b/src/core/client/framework/testHelpers/denormalize.ts new file mode 100644 index 000000000..7b3bbbcdf --- /dev/null +++ b/src/core/client/framework/testHelpers/denormalize.ts @@ -0,0 +1,48 @@ +export function denormalizeComment(comment: any, parents: any[] = []) { + const replyNodes = + (comment.replies && + comment.replies.edges.map((edge: any) => + denormalizeComment(edge, [...parents, comment]) + )) || + []; + const repliesPageInfo = (comment.replies && comment.replies.pageInfo) || { + endCursor: null, + hasNextPage: false, + }; + return { + ...comment, + replies: { edges: replyNodes, pageInfo: repliesPageInfo }, + replyCount: replyNodes.length, + parentCount: parents.length, + parents: { + edges: parents, + pageInfo: { startCursor: null, hasPreviousPage: false }, + }, + }; +} + +export function denormalizeComments(commentList: any[]) { + return commentList.map(c => denormalizeComment(c)); +} + +export function denormalizeAsset(asset: any) { + const commentNodes = + (asset.comments && + asset.comments.edges.map((edge: any) => denormalizeComment(edge))) || + []; + const commentsPageInfo = (asset.comments && asset.comments.pageInfo) || { + endCursor: null, + hasNextPage: false, + }; + return { + ...asset, + comments: { edges: commentNodes, pageInfo: commentsPageInfo }, + commentCounts: { + totalVisible: commentNodes.length, + }, + }; +} + +export function denormalizeAssets(assetList: any[]) { + return assetList.map(a => denormalizeAsset(a)); +} diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts index 811e1ba19..9dec944d2 100644 --- a/src/core/client/framework/testHelpers/index.ts +++ b/src/core/client/framework/testHelpers/index.ts @@ -9,3 +9,4 @@ export { NoFragmentRefs, } from "./removeFragmentRefs"; export { default as createUUIDGenerator } from "./createUUIDGenerator"; +export * from "./denormalize"; diff --git a/src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx b/src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx index 20132c1f7..dfe5af8f2 100644 --- a/src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx +++ b/src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx @@ -1,4 +1,5 @@ import { shallow } from "enzyme"; +import qs from "query-string"; import React from "react"; import { Environment, RecordSource } from "relay-runtime"; @@ -10,12 +11,20 @@ import { OnPymSetCommentID } from "./OnPymSetCommentID"; let relayEnvironment: Environment; const source: RecordSource = new RecordSource(); +const previousLocation = location.toString(); +const previousState = window.history.state; + beforeAll(() => { relayEnvironment = createRelayEnvironment({ source, }); }); +afterEach(() => { + // As history will change after the listener triggers, reset this to before. + window.history.replaceState(previousState, document.title, previousLocation); +}); + it("Sets comment id", () => { const id = "comment1-id"; const props = { @@ -29,6 +38,7 @@ it("Sets comment id", () => { }; shallow(); expect(source.get(LOCAL_ID)!.commentID).toEqual(id); + expect(qs.parse(location.search).commentID).toEqual(id); }); it("Sets comment id to null when empty", () => { @@ -44,4 +54,5 @@ it("Sets comment id to null when empty", () => { }; shallow(); expect(source.get(LOCAL_ID)!.commentID).toEqual(null); + expect(qs.parse(location.search).commentID).toBeUndefined(); }); diff --git a/src/core/client/stream/listeners/OnPymSetCommentID.ts b/src/core/client/stream/listeners/OnPymSetCommentID.ts index f9ca0c6f5..1c485f113 100644 --- a/src/core/client/stream/listeners/OnPymSetCommentID.ts +++ b/src/core/client/stream/listeners/OnPymSetCommentID.ts @@ -3,6 +3,7 @@ import { Component } from "react"; import { commitLocalUpdate } from "react-relay"; import { Environment } from "relay-runtime"; +import { getURLWithCommentID } from "talk-framework/helpers"; import { withContext } from "talk-framework/lib/bootstrap"; import { LOCAL_ID } from "talk-framework/lib/relay"; @@ -21,6 +22,15 @@ export class OnPymSetCommentID extends Component { const id = raw || null; if (s.get(LOCAL_ID)!.getValue("commentID") !== id) { s.get(LOCAL_ID)!.setValue(id, "commentID"); + + // Change iframe url, this is important + // because it is used to cleanly initialized + // a user session. + window.history.replaceState( + window.history.state, + document.title, + getURLWithCommentID(location.href, id || undefined) + ); } }); }); diff --git a/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts b/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts index 8f296c79d..573d4cd0a 100644 --- a/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts +++ b/src/core/client/stream/mutations/SetCommentIDMutation.spec.ts @@ -1,3 +1,4 @@ +import qs from "query-string"; import { Environment, RecordSource } from "relay-runtime"; import sinon from "sinon"; @@ -16,10 +17,19 @@ beforeAll(() => { }); }); +const previousLocation = location.toString(); +const previousState = window.history.state; + +afterEach(() => { + // As history will change after the listener triggers, reset this to before. + window.history.replaceState(previousState, document.title, previousLocation); +}); + it("Sets comment id", () => { const id = "comment1-id"; commit(environment, { id }, {} as any); expect(source.get(LOCAL_ID)!.commentID).toEqual(id); + expect(qs.parse(location.search).commentID).toEqual(id); }); it("Should call setCommentID in pym", async () => { @@ -50,5 +60,6 @@ it("Should call setCommentID in pym with empty id", async () => { commit(environment, { id: null }, context as any); await timeout(); expect(source.get(LOCAL_ID)!.commentID).toEqual(null); + expect(qs.parse(location.search).commentID).toBeUndefined(); context.pym.sendMessage.verify(); }); diff --git a/src/core/client/stream/mutations/SetCommentIDMutation.ts b/src/core/client/stream/mutations/SetCommentIDMutation.ts index eb356286b..5bf668f1f 100644 --- a/src/core/client/stream/mutations/SetCommentIDMutation.ts +++ b/src/core/client/stream/mutations/SetCommentIDMutation.ts @@ -1,5 +1,6 @@ import { commitLocalUpdate, Environment } from "relay-runtime"; +import { getURLWithCommentID } from "talk-framework/helpers"; import { TalkContext } from "talk-framework/lib/bootstrap"; import { createMutationContainer } from "talk-framework/lib/relay"; import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer"; @@ -19,6 +20,16 @@ export async function commit( const record = store.get(LOCAL_ID)!; record.setValue(input.id, "commentID"); record.setValue("COMMENTS", "activeTab"); + + // Change iframe url, this is important + // because it is used to cleanly initialized + // a user session. + window.history.replaceState( + window.history.state, + document.title, + getURLWithCommentID(location.href, input.id || undefined) + ); + if (pym) { // This sets the comment id on the parent url. pym.sendMessage("setCommentID", input.id || ""); diff --git a/src/core/client/stream/tabs/comments/components/Comment/Comment.css b/src/core/client/stream/tabs/comments/components/Comment/Comment.css index 2b9c0d5bb..515dfe5d1 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.css +++ b/src/core/client/stream/tabs/comments/components/Comment/Comment.css @@ -1,5 +1,9 @@ .root { } +.highlight { + background-color: var(--palette-primary-lightest); + padding: var(--spacing-unit); +} .topBar { margin-bottom: calc(0.5 * var(--spacing-unit)); } 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 a793f4d1c..03b5f99a9 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 @@ -8,9 +8,7 @@ import Comment from "./Comment"; it("renders username and body", () => { const props: PropTypesOf = { id: "comment-id", - author: { - username: "Marvin", - }, + username: "Marvin", body: "Woof", createdAt: "1995-12-17T03:24:00.000Z", topBarRight: "topBarRight", 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 7ed3d93f9..6ebb427b9 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx @@ -1,3 +1,4 @@ +import cn from "classnames"; import React, { StatelessComponent } from "react"; import HTMLContent from "talk-stream/components/HTMLContent"; @@ -12,19 +13,21 @@ import Username from "./Username"; export interface CommentProps { id?: string; className?: string; - author: { - username: string | null; - } | null; + username: string | null; body: string | null; createdAt: string; topBarRight?: React.ReactNode; footer?: React.ReactNode; showEditedMarker?: boolean; + highlight?: boolean; } const Comment: StatelessComponent = props => { return ( -
+
= props => { id={props.id} > - {props.author && - props.author.username && ( - {props.author.username} - )} + {props.username && {props.username}} {props.createdAt} {props.showEditedMarker && } diff --git a/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.spec.tsx b/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.spec.tsx index be489a804..6417f9d9a 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.spec.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.spec.tsx @@ -8,9 +8,7 @@ import IndentedComment from "./IndentedComment"; it("renders correctly", () => { const props: PropTypesOf = { indentLevel: 1, - author: { - username: "Marvin", - }, + username: "Marvin", body: "Woof", createdAt: "1995-12-17T03:24:00.000Z", }; diff --git a/src/core/client/stream/tabs/comments/components/Comment/RootParent.tsx b/src/core/client/stream/tabs/comments/components/Comment/RootParent.tsx new file mode 100644 index 000000000..04134c7ee --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Comment/RootParent.tsx @@ -0,0 +1,28 @@ +import React, { StatelessComponent } from "react"; + +import Timestamp from "talk-stream/components/Timestamp"; +import { Flex } from "talk-ui/components"; + +import TopBarLeft from "./TopBarLeft"; +import Username from "./Username"; + +export interface RootParentProps { + id?: string; + username: string | null; + createdAt: string; +} + +const RootParent: StatelessComponent = props => { + return ( + + + {props.username && {props.username}} + + {props.createdAt} + + + + ); +}; + +export default RootParent; diff --git a/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap index daaa79aca..3f9f26e99 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap @@ -6,13 +6,9 @@ exports[`renders correctly 1`] = ` level={1} > `; diff --git a/src/core/client/stream/tabs/comments/components/Comment/index.ts b/src/core/client/stream/tabs/comments/components/Comment/index.ts index d05ab70f8..b6d4f3210 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/index.ts +++ b/src/core/client/stream/tabs/comments/components/Comment/index.ts @@ -3,3 +3,4 @@ export { default as TopBarLeft } from "./TopBarLeft"; export { default as Username } from "./Username"; export { default as ButtonsBar } from "./ButtonsBar"; export { default as ShowConversationLink } from "./ShowConversationLink"; +export { default as RootParent } from "./RootParent"; diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.css b/src/core/client/stream/tabs/comments/components/ConversationThread.css new file mode 100644 index 000000000..3074b5320 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ConversationThread.css @@ -0,0 +1,7 @@ +.root { +} + +.loadMore { + padding-left: var(--spacing-unit); + padding-bottom: var(--spacing-unit); +} diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.spec.tsx b/src/core/client/stream/tabs/comments/components/ConversationThread.spec.tsx new file mode 100644 index 000000000..0812cf786 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ConversationThread.spec.tsx @@ -0,0 +1,70 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import { removeFragmentRefs } from "talk-framework/testHelpers"; +import ConversationThread from "./ConversationThread"; + +const ConversationThreadN = removeFragmentRefs(ConversationThread); + +describe("with 2 remaining parent comments", () => { + it("renders correctly", () => { + const props: PropTypesOf = { + className: "root", + me: {}, + asset: {}, + settings: {}, + comment: {}, + disableLoadMore: false, + loadMore: noop, + remaining: 2, + parents: [], + rootParent: { + id: "root-parent", + createdAt: "1995-12-17T03:24:00.000Z", + username: "parentAuthor", + }, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + it("renders with disabled load more", () => { + const props: PropTypesOf = { + className: "root", + me: {}, + asset: {}, + settings: {}, + comment: {}, + disableLoadMore: true, + loadMore: noop, + remaining: 2, + parents: [], + rootParent: { + id: "root-parent", + createdAt: "1995-12-17T03:24:00.000Z", + username: "parentAuthor", + }, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); + +it("renders with no parent comments", () => { + const props: PropTypesOf = { + className: "root", + me: {}, + asset: {}, + settings: {}, + comment: {}, + disableLoadMore: false, + loadMore: noop, + remaining: 0, + parents: [], + rootParent: null, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.tsx b/src/core/client/stream/tabs/comments/components/ConversationThread.tsx new file mode 100644 index 000000000..61ae17708 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/ConversationThread.tsx @@ -0,0 +1,122 @@ +import cn from "classnames"; +import { Localized } from "fluent-react/compat"; +import React, { StatelessComponent } from "react"; + +import { PropTypesOf } from "talk-framework/types"; +import { Button, Flex, HorizontalGutter } from "talk-ui/components"; + +import CommentContainer from "../containers/CommentContainer"; +import LocalReplyListContainer from "../containers/LocalReplyListContainer"; +import { RootParent } from "./Comment"; +import * as styles from "./ConversationThread.css"; +import Counter from "./Counter"; +import { Circle, Line } from "./Timeline"; + +export interface ConversationThreadProps { + className?: string; + me: PropTypesOf["me"] & + (PropTypesOf["me"] | null); + asset: PropTypesOf["asset"] & + PropTypesOf["asset"]; + settings: PropTypesOf["settings"] & + PropTypesOf["settings"]; + comment: PropTypesOf["comment"]; + disableLoadMore: boolean; + loadMore: () => void; + remaining: number; + parents: Array< + { id: string } & PropTypesOf["comment"] & + PropTypesOf["comment"] + >; + rootParent: { + id: string; + createdAt: string; + username: string | null; + } | null; +} + +const ConversationThread: StatelessComponent< + ConversationThreadProps +> = props => { + if (props.remaining === 0 && props.parents.length === 0) { + return ( +
+ +
+ ); + } + return ( +
+ }> + {props.rootParent && ( + + + + )} + {props.remaining > 0 && ( + + + + + + {props.remaining > 1 && {props.remaining}} + + + )} + + + {props.parents.map((parent, i) => ( + 0}> + + {props.me && ( + + )} + + ))} + + + + +
+ ); +}; + +export default ConversationThread; diff --git a/src/core/client/stream/tabs/comments/components/Counter.spec.tsx b/src/core/client/stream/tabs/comments/components/Counter.spec.tsx new file mode 100644 index 000000000..d8e963b97 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Counter.spec.tsx @@ -0,0 +1,9 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import Counter from "./Counter"; + +it("renders correctly", () => { + const wrapper = shallow(20); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/comments/components/Counter.tsx b/src/core/client/stream/tabs/comments/components/Counter.tsx new file mode 100644 index 000000000..bcf64e0cf --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Counter.tsx @@ -0,0 +1,16 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import * as styles from "./Counter.css"; + +interface Props { + className?: string; +} + +const Counter: StatelessComponent = props => { + return ( + {props.children} + ); +}; + +export default Counter; diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.css b/src/core/client/stream/tabs/comments/components/PermalinkView.css index a71a33af8..ef94e4f5a 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkView.css +++ b/src/core/client/stream/tabs/comments/components/PermalinkView.css @@ -2,6 +2,23 @@ width: 100%; } -.button { - margin-bottom: calc(2 * var(--spacing-unit)); +.replyList { + padding-left: calc(2 * var(--spacing-unit)); +} + +.title1 { + font-size: calc(14rem / var(--rem-base)); + font-weight: var(--font-weight-medium); + font-family: var(--font-family-sans-serif); + line-height: calc(20em / 16); + letter-spacing: calc(0.2em / 16); + color: var(--palette-text-primary); +} +.title2 { + font-size: calc(18rem / var(--rem-base)); + font-weight: var(--font-weight-medium); + font-family: var(--font-family-sans-serif); + line-height: calc(20em / 16); + letter-spacing: calc(0.2em / 16); + color: var(--palette-text-primary); } diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.spec.tsx b/src/core/client/stream/tabs/comments/components/PermalinkView.spec.tsx new file mode 100644 index 000000000..62475304f --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/PermalinkView.spec.tsx @@ -0,0 +1,36 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import { removeFragmentRefs } from "talk-framework/testHelpers"; +import PermalinkView from "./PermalinkView"; + +const PermalinkViewN = removeFragmentRefs(PermalinkView); + +it("renders correctly", () => { + const props: PropTypesOf = { + me: {}, + asset: {}, + settings: {}, + comment: {}, + showAllCommentsHref: "http://localhost/link", + onShowAllComments: noop, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders comment not found", () => { + const props: PropTypesOf = { + me: {}, + asset: {}, + settings: {}, + comment: null, + showAllCommentsHref: "http://localhost/link", + onShowAllComments: noop, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx b/src/core/client/stream/tabs/comments/components/PermalinkView.tsx index 475483bb1..b036207de 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx +++ b/src/core/client/stream/tabs/comments/components/PermalinkView.tsx @@ -2,16 +2,25 @@ import { Localized } from "fluent-react/compat"; import React, { MouseEvent, StatelessComponent } from "react"; import { PropTypesOf } from "talk-framework/types"; -import { Button, Typography } from "talk-ui/components"; +import { Button, Flex, HorizontalGutter, Typography } from "talk-ui/components"; -import CommentContainer from "../containers/CommentContainer"; +import UserBoxContainer from "../../../containers/UserBoxContainer"; +import ConversationThreadContainer from "../containers/ConversationThreadContainer"; +import ReplyListContainer from "../containers/ReplyListContainer"; import * as styles from "./PermalinkView.css"; export interface PermalinkViewProps { - me: PropTypesOf["me"]; - asset: PropTypesOf["asset"]; - settings: PropTypesOf["settings"]; - comment: PropTypesOf["comment"] | null; + me: PropTypesOf["me"] & + PropTypesOf["me"] & + PropTypesOf["me"]; + asset: PropTypesOf["asset"] & + PropTypesOf["asset"]; + comment: + | PropTypesOf["comment"] & + PropTypesOf["comment"] + | null; + settings: PropTypesOf["settings"] & + PropTypesOf["settings"]; showAllCommentsHref: string | null; onShowAllComments: (e: MouseEvent) => void; } @@ -25,38 +34,57 @@ const PermalinkView: StatelessComponent = ({ me, }) => { return ( -
- {showAllCommentsHref && ( - - + + + + + + You are currently viewing a + - )} + + SINGLE CONVERSATION + + {showAllCommentsHref && ( + + + + )} + {!comment && ( Comment not found )} {comment && ( - + + +
+ +
+
)} -
+ ); }; diff --git a/src/core/client/stream/tabs/comments/components/RTE.css b/src/core/client/stream/tabs/comments/components/RTE.css index 7922d5ddd..c0ed9f4fc 100644 --- a/src/core/client/stream/tabs/comments/components/RTE.css +++ b/src/core/client/stream/tabs/comments/components/RTE.css @@ -2,6 +2,10 @@ composes: root from "talk-stream/shared/htmlContent.css"; } +.toolbar { + background-color: var(--palette-common-white); +} + .placeholder { composes: placeholder from "talk-ui/shared/typography.css"; margin: var(--spacing-unit) 0 0 calc(1px + var(--spacing-unit)); diff --git a/src/core/client/stream/tabs/comments/components/RTE.tsx b/src/core/client/stream/tabs/comments/components/RTE.tsx index 0feac5db9..f98eea872 100644 --- a/src/core/client/stream/tabs/comments/components/RTE.tsx +++ b/src/core/client/stream/tabs/comments/components/RTE.tsx @@ -95,6 +95,7 @@ const RTE: StatelessComponent = props => { className={className} contentClassName={styles.content} placeholderClassName={styles.placeholder} + toolbarClassName={styles.toolbar} onChange={onChange} value={value || defaultValue} disabled={disabled} diff --git a/src/core/client/stream/tabs/comments/components/Timeline/Circle.css b/src/core/client/stream/tabs/comments/components/Timeline/Circle.css new file mode 100644 index 000000000..0611ee52b --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/Circle.css @@ -0,0 +1,31 @@ +.circleContainer { + position: relative; +} + +.circleSubContainer { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + left: calc(-1 * var(--spacing-unit) - var(--spacing-unit) - 1px); + background-color: var(--palette-common-white); + width: calc(2 * var(--spacing-unit)); + height: 23px; + color: var(--palette-grey-main); +} + +.end { + align-items: flex-start; + padding-top: 8px; + height: 100%; + @media (min-width: $breakpoints-xs) { + padding-top: 6px; + } +} + +.circle { + width: var(--spacing-unit); + height: var(--spacing-unit); + fill: currentColor; + stroke: currentColor; +} diff --git a/src/core/client/stream/tabs/comments/components/Timeline/Circle.spec.tsx b/src/core/client/stream/tabs/comments/components/Timeline/Circle.spec.tsx new file mode 100644 index 000000000..3f564f88f --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/Circle.spec.tsx @@ -0,0 +1,16 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import Circle from "./Circle"; + +it("renders correctly", () => { + const props: PropTypesOf = { + className: "root", + hollow: true, + end: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/comments/components/Timeline/Circle.tsx b/src/core/client/stream/tabs/comments/components/Timeline/Circle.tsx new file mode 100644 index 000000000..851a243eb --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/Circle.tsx @@ -0,0 +1,35 @@ +import cn from "classnames"; +import * as React from "react"; +import { StatelessComponent } from "react"; + +import styles from "./Circle.css"; + +export interface CircleProps { + className?: string; + hollow?: boolean; + end?: boolean; +} + +const Circle: StatelessComponent = props => { + return ( +
+
+ + {props.hollow && ( + + )} + {!props.hollow && } + +
+ {props.children} +
+ ); +}; + +export default Circle; diff --git a/src/core/client/stream/tabs/comments/components/Timeline/Line.css b/src/core/client/stream/tabs/comments/components/Timeline/Line.css new file mode 100644 index 000000000..6a6b7bdc9 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/Line.css @@ -0,0 +1,9 @@ +.root { + border-left: 2px solid var(--palette-grey-main); + padding-left: var(--spacing-unit); + margin-left: calc(0.5 * var(--spacing-unit)); +} + +.dotted { + border-left-style: dotted; +} diff --git a/src/core/client/stream/tabs/comments/components/Timeline/Line.spec.tsx b/src/core/client/stream/tabs/comments/components/Timeline/Line.spec.tsx new file mode 100644 index 000000000..0231bb0c8 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/Line.spec.tsx @@ -0,0 +1,15 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import Line from "./Line"; + +it("renders correctly", () => { + const props: PropTypesOf = { + className: "root", + dotted: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/comments/components/Timeline/Line.tsx b/src/core/client/stream/tabs/comments/components/Timeline/Line.tsx new file mode 100644 index 000000000..ed03c2169 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/Line.tsx @@ -0,0 +1,24 @@ +import cn from "classnames"; +import * as React from "react"; +import { StatelessComponent } from "react"; + +import styles from "./Line.css"; + +interface LineProps { + className?: string; + dotted?: boolean; +} + +const Line: StatelessComponent = props => { + return ( +
+ {props.children} +
+ ); +}; + +export default Line; diff --git a/src/core/client/stream/tabs/comments/components/Timeline/__snapshots__/Circle.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/Timeline/__snapshots__/Circle.spec.tsx.snap new file mode 100644 index 000000000..fdffb4a26 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/__snapshots__/Circle.spec.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+
+ + + +
+
+`; diff --git a/src/core/client/stream/tabs/comments/components/Timeline/__snapshots__/Line.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/Timeline/__snapshots__/Line.spec.tsx.snap new file mode 100644 index 000000000..c22ace86c --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/__snapshots__/Line.spec.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+`; diff --git a/src/core/client/stream/tabs/comments/components/Timeline/index.ts b/src/core/client/stream/tabs/comments/components/Timeline/index.ts new file mode 100644 index 000000000..7eda1e131 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/Timeline/index.ts @@ -0,0 +1,2 @@ +export { default as Circle } from "./Circle"; +export { default as Line } from "./Line"; diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap new file mode 100644 index 000000000..604420bfa --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap @@ -0,0 +1,141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders with no parent comments 1`] = ` +
+ +
+`; + +exports[`with 2 remaining parent comments renders correctly 1`] = ` +
+ + } + > + + + + + + + + Show hidden comments + + + + 2 + + + + + + + + + +
+`; + +exports[`with 2 remaining parent comments renders with disabled load more 1`] = ` +
+ + } + > + + + + + + + + Show hidden comments + + + + 2 + + + + + + + + + +
+`; diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/Counter.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/Counter.spec.tsx.snap new file mode 100644 index 000000000..17fb6637b --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/Counter.spec.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + 20 + +`; diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/PermalinkView.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/PermalinkView.spec.tsx.snap new file mode 100644 index 000000000..1bb7fdc42 --- /dev/null +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/PermalinkView.spec.tsx.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders comment not found 1`] = ` + + + + + + You are currently viewing a + + + + + SINGLE CONVERSATION + + + + + View Full Discussion + + + + + + Comment not found + + + +`; + +exports[`renders correctly 1`] = ` + + + + + + You are currently viewing a + + + + + SINGLE CONVERSATION + + + + + View Full Discussion + + + + + +
+ +
+
+
+`; diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/RTE.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/RTE.spec.tsx.snap index 322439aab..7c91ecaa8 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/RTE.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/RTE.spec.tsx.snap @@ -62,7 +62,7 @@ exports[`renders correctly 1`] = ` placeholder="Post a comment" placeholderClassName="RTE-placeholder" placeholderClassNameDisabled="" - toolbarClassName="" + toolbarClassName="RTE-toolbar" toolbarClassNameDisabled="" value="Hello world" /> diff --git a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx index 2ca29a7fd..9516d10ac 100644 --- a/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/CommentContainer.tsx @@ -17,8 +17,9 @@ import { withShowAuthPopupMutation, } from "talk-stream/mutations"; -import ReactionButtonContainer from "talk-stream/tabs/comments/containers/ReactionButtonContainer"; -import { Button } from "talk-ui/components"; +import { Button, HorizontalGutter } from "talk-ui/components"; +import ReactionButtonContainer from "./ReactionButtonContainer"; + import Comment, { ButtonsBar, ShowConversationLink, @@ -45,6 +46,7 @@ interface InnerProps { disableReplies?: boolean; /** showConversationLink will render a link to the conversation */ showConversationLink?: boolean; + highlight?: boolean; } interface State { @@ -140,6 +142,7 @@ export class CommentContainer extends Component { localReply, disableReplies, showConversationLink, + highlight, me, } = this.props; const { showReplyDialog, showEditDialog, editable } = this.state; @@ -152,15 +155,16 @@ export class CommentContainer extends Component { ); } return ( - <> + @@ -216,7 +220,7 @@ export class CommentContainer extends Component { localReply={localReply} /> )} - + ); } } diff --git a/src/core/client/stream/tabs/comments/containers/ConversationThreadContainer.tsx b/src/core/client/stream/tabs/comments/containers/ConversationThreadContainer.tsx new file mode 100644 index 000000000..2d31d97eb --- /dev/null +++ b/src/core/client/stream/tabs/comments/containers/ConversationThreadContainer.tsx @@ -0,0 +1,186 @@ +import { Child as PymChild } from "pym.js"; +import React from "react"; +import { graphql, RelayPaginationProp } from "react-relay"; + +import { withContext } from "talk-framework/lib/bootstrap"; +import { withPaginationContainer } from "talk-framework/lib/relay"; +import { ConversationThreadContainer_asset as AssetData } from "talk-stream/__generated__/ConversationThreadContainer_asset.graphql"; +import { ConversationThreadContainer_comment as CommentData } from "talk-stream/__generated__/ConversationThreadContainer_comment.graphql"; +import { ConversationThreadContainer_me as MeData } from "talk-stream/__generated__/ConversationThreadContainer_me.graphql"; +import { ConversationThreadContainer_settings as SettingsData } from "talk-stream/__generated__/ConversationThreadContainer_settings.graphql"; +import { ConversationThreadContainerPaginationQueryVariables } from "talk-stream/__generated__/ConversationThreadContainerPaginationQuery.graphql"; +import { + SetCommentIDMutation, + withSetCommentIDMutation, +} from "talk-stream/mutations"; + +import ConversationThread from "../components/ConversationThread"; + +interface ConversationThreadContainerProps { + comment: CommentData; + asset: AssetData; + settings: SettingsData; + me: MeData | null; + setCommentID: SetCommentIDMutation; + pym: PymChild | undefined; + relay: RelayPaginationProp; +} + +class ConversationThreadContainer extends React.Component< + ConversationThreadContainerProps +> { + public state = { + disableLoadMore: false, + }; + + private loadMore = () => { + if (!this.props.relay.hasMore() || this.props.relay.isLoading()) { + return; + } + this.setState({ disableLoadMore: true }); + this.props.relay.loadMore( + 5, // Fetch the next 5 feed items + error => { + this.setState({ disableLoadMore: false }); + if (error) { + // tslint:disable-next-line:no-console + console.error(error); + } + } + ); + }; + + public render() { + const { comment, asset, me, settings } = this.props; + const hasMore = this.props.relay.hasMore(); + return ( + edge.node)} + disableLoadMore={this.state.disableLoadMore} + loadMore={this.loadMore} + remaining={comment.parentCount - comment.parents.edges.length} + rootParent={ + (hasMore && + comment && + comment.rootParent && { + id: comment.rootParent.id, + createdAt: comment.rootParent.createdAt, + username: + comment.rootParent.author && comment.rootParent.author.username, + }) || + null + } + /> + ); + } +} + +// TODO: (cvle) This should be autogenerated. +interface FragmentVariables { + count: number; + cursor?: string; +} + +const enhanced = withContext(ctx => ({ + pym: ctx.pym, +}))( + withSetCommentIDMutation( + withPaginationContainer< + ConversationThreadContainerProps, + ConversationThreadContainerPaginationQueryVariables, + FragmentVariables + >( + { + asset: graphql` + fragment ConversationThreadContainer_asset on Asset { + ...CommentContainer_asset + ...LocalReplyListContainer_asset + } + `, + settings: graphql` + fragment ConversationThreadContainer_settings on Settings { + ...CommentContainer_settings + ...LocalReplyListContainer_settings + } + `, + comment: graphql` + fragment ConversationThreadContainer_comment on Comment + @argumentDefinitions( + count: { type: "Int!", defaultValue: 0 } + cursor: { type: "Cursor" } + ) { + id + ...CommentContainer_comment + rootParent { + id + author { + id + username + } + createdAt + } + parentCount + parents(last: $count, before: $cursor) + @connection(key: "ConversationThread_parents") { + edges { + node { + id + ...CommentContainer_comment + ...LocalReplyListContainer_comment + } + } + } + } + `, + me: graphql` + fragment ConversationThreadContainer_me on User { + ...CommentContainer_me + ...LocalReplyListContainer_me + } + `, + }, + { + direction: "backward", + getConnectionFromProps(props) { + return props.comment && props.comment.parents; + }, + // This is also the default implementation of `getFragmentVariables` if it isn't provided. + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + count: totalCount, + }; + }, + getVariables(props, { count, cursor }) { + return { + count, + cursor, + // commentID isn't specified as an @argument for the fragment, but it should be a + // variable available for the fragment under the query root. + commentID: props.comment.id, + }; + }, + query: graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query ConversationThreadContainerPaginationQuery( + $count: Int! + $cursor: Cursor + $commentID: ID! + ) { + comment(id: $commentID) { + ...ConversationThreadContainer_comment + @arguments(count: $count, cursor: $cursor) + } + } + `, + } + )(ConversationThreadContainer) + ) +); + +export default enhanced; diff --git a/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx b/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx index 4354012cd..cc0c0719c 100644 --- a/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx +++ b/src/core/client/stream/tabs/comments/containers/PermalinkViewContainer.tsx @@ -40,12 +40,7 @@ class PermalinkViewContainer extends React.Component< public componentDidMount() { if (this.props.pym) { - const scrollTo = this.props.comment - ? document - .getElementById(`comment-${this.props.comment.id}`)! - .getBoundingClientRect().top + window.pageYOffset - : 50; - setTimeout(() => this.props.pym!.scrollParentToChildPos(scrollTo), 100); + setTimeout(() => this.props.pym!.scrollParentToChildPos(0), 100); } } @@ -71,23 +66,28 @@ const enhanced = withContext(ctx => ({ withFragmentContainer({ asset: graphql` fragment PermalinkViewContainer_asset on Asset { - ...CommentContainer_asset + ...ConversationThreadContainer_asset + ...ReplyListContainer1_asset } `, comment: graphql` fragment PermalinkViewContainer_comment on Comment { id - ...CommentContainer_comment + ...ConversationThreadContainer_comment + ...ReplyListContainer1_comment } `, me: graphql` fragment PermalinkViewContainer_me on User { - ...CommentContainer_me + ...ConversationThreadContainer_me + ...ReplyListContainer1_me + ...UserBoxContainer_me } `, settings: graphql` fragment PermalinkViewContainer_settings on Settings { - ...CommentContainer_settings + ...ConversationThreadContainer_settings + ...ReplyListContainer1_settings } `, })(PermalinkViewContainer) 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 5c96e25d7..e1c085015 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,14 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`hide reply button 1`] = ` - + - + `; exports[`renders body only 1`] = ` - + - + `; exports[`renders username and body 1`] = ` - + - + `; exports[`shows conversation link 1`] = ` - + - + `; diff --git a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap index d08ad4599..d5426a89a 100644 --- a/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/editComment.spec.tsx.snap @@ -125,7 +125,7 @@ exports[`cancel edit: edit canceled 1`] = ` className="" >
-
- -
-
-
-
- - + + +
@@ -348,85 +352,89 @@ exports[`cancel edit: edit canceled 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -564,7 +572,7 @@ exports[`edit a comment: edit form 1`] = ` className="" >
- + + +
@@ -1063,7 +1075,7 @@ exports[`edit a comment: optimistic response 1`] = ` className="" >
- + + +
@@ -1562,7 +1578,7 @@ exports[`edit a comment: render stream 1`] = ` className="" >
-
- -
- -
-
- - + + +
@@ -1785,85 +1805,89 @@ exports[`edit a comment: render stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -2001,7 +2025,7 @@ exports[`edit a comment: server response 1`] = ` className="" >
+
-
- -
-
-
-
- - + + +
@@ -2233,85 +2261,89 @@ exports[`edit a comment: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -2449,7 +2481,7 @@ exports[`shows expiry message: edit form closed 1`] = ` className="" >
-
- -
-
-
-
- - + + +
@@ -2672,85 +2708,89 @@ exports[`shows expiry message: edit form closed 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -2888,7 +2928,7 @@ exports[`shows expiry message: edit time expired 1`] = ` className="" >
- + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap index 2a93734a8..5f318e2bb 100644 --- a/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/loadMore.spec.tsx.snap @@ -84,7 +84,7 @@ exports[`loads more comments 1`] = ` className="" >
- + + +
@@ -271,85 +275,89 @@ exports[`loads more comments 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -360,85 +368,89 @@ exports[`loads more comments 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Isabelle -
- + Isabelle + +
+ +
-
-
-
- - + + +
@@ -535,7 +547,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -722,85 +738,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap index 49a759525..0aec18559 100644 --- a/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkView.spec.tsx.snap @@ -38,56 +38,506 @@ exports[`renders permalink view 1`] = ` role="tabpanel" >
- - Show All Comments -
+ + +
+
+

+ You are currently viewing a +

+

+ SINGLE CONVERSATION +

+ + View Full Discussion + +
+
+
- - Markus - + + +
+
- +
+
+
+ + Lukas + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+
+ + +
+
+
+
@@ -95,47 +545,93 @@ exports[`renders permalink view 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
-
- - +
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
@@ -230,7 +726,7 @@ exports[`show all comments 1`] = ` className="" >
+
+
+
+
+
+
+
-
- -
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
- - Respect - - +
+
+ + Isabelle + +
+ +
+
+
+
+
+
+ + +
+
+
diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap index d6e998281..fa96a0137 100644 --- a/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkViewCommentNotFound.spec.tsx.snap @@ -38,24 +38,68 @@ exports[`renders permalink view with unknown comment 1`] = ` role="tabpanel" >
- - Show All Comments - + + +
+
+

+ You are currently viewing a +

+

+ SINGLE CONVERSATION +

+ + View Full Discussion + +

@@ -150,7 +194,7 @@ exports[`show all comments 1`] = ` className="" >

- + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap new file mode 100644 index 000000000..d3b65965f --- /dev/null +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap @@ -0,0 +1,771 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders permalink view 1`] = ` +
+
    + +
+
+
+
+ + +
+
+

+ You are currently viewing a +

+

+ SINGLE CONVERSATION +

+ + View Full Discussion + +
+
+
+
+
+
+ + + +
+
+
+ + Isabelle + +
+ +
+
+
+
+
+
+ + + +
+
+ + + 2 + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`views pervious comments 1`] = ` +
+
    + +
+
+
+
+ + +
+
+

+ You are currently viewing a +

+

+ SINGLE CONVERSATION +

+ + View Full Discussion + +
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ + Lukas + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ + Isabelle + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ + Markus + +
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; diff --git a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap index b9c12183b..5760fd0d6 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postComment.spec.tsx.snap @@ -125,7 +125,7 @@ exports[`post a comment: optimistic response 1`] = ` className="" >
-
- -
-
-
Hello world!", - } - } - /> -
- - + + +
@@ -342,85 +346,89 @@ exports[`post a comment: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -431,85 +439,89 @@ exports[`post a comment: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -647,7 +659,7 @@ exports[`post a comment: server response 1`] = ` className="" >
- + + +
@@ -854,85 +870,89 @@ exports[`post a comment: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -943,85 +963,89 @@ exports[`post a comment: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -1159,7 +1183,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -1366,85 +1394,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap index 2ee9aa210..92fc9bc6f 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postLocalReply.spec.tsx.snap @@ -125,7 +125,7 @@ exports[`post a reply: open reply form 1`] = ` className="" >
- + + +
@@ -336,85 +340,89 @@ exports[`post a reply: open reply form 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -429,85 +437,89 @@ exports[`post a reply: open reply form 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -522,85 +534,89 @@ exports[`post a reply: open reply form 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -615,85 +631,89 @@ exports[`post a reply: open reply form 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -708,90 +728,209 @@ exports[`post a reply: open reply form 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
+
+
+
+
+
+ + +
+ + Read More of this Conversation > + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+ +
-
-
- - -
- - Read More of this Conversation > - -
-
-
-
- -
-
- -
-
+ - - -
- -
-
+ Submit +
-
- - -
-
-
+ +
@@ -1077,7 +1101,7 @@ exports[`post a reply: optimistic response 1`] = ` className="" >
- + + +
@@ -1288,85 +1316,89 @@ exports[`post a reply: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -1381,85 +1413,89 @@ exports[`post a reply: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -1474,85 +1510,89 @@ exports[`post a reply: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -1567,85 +1607,89 @@ exports[`post a reply: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -1660,90 +1704,203 @@ exports[`post a reply: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
+
+
+
+
+
+ + +
+ + Read More of this Conversation > + +
+
+
+
+
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={true} + id="comments-replyCommentForm-rte-comment-with-deepest-replies-5" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + />
-
-
- - -
- - Read More of this Conversation > - -
-
-
-
- -
-
- -
-
+ - - -
-
Hello world!", - } - } - disabled={true} - id="comments-replyCommentForm-rte-comment-with-deepest-replies-5" - onBlur={[Function]} - onChange={[Function]} - onCut={[Function]} - onFocus={[Function]} - onInput={[Function]} - onKeyDown={[Function]} - onPaste={[Function]} - onSelect={[Function]} - /> -
+ Submit +
-
- - -
-
-
+ +
- - Markus -
- + Markus + +
+ +
+
+
+
-
- -
-
-
Hello world!", - } - } - /> -
- + +
@@ -2118,7 +2170,7 @@ exports[`post a reply: server response 1`] = ` className="" >
- + + +
@@ -2329,85 +2385,89 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -2422,85 +2482,89 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -2515,85 +2579,89 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -2608,85 +2676,89 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -2701,57 +2773,93 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
@@ -2810,69 +2886,73 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
Hello world! (from server)", - } - } - /> -
- + +
@@ -3022,7 +3102,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -3233,85 +3317,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -3326,85 +3414,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -3419,85 +3511,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -3512,85 +3608,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -3605,57 +3705,93 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
diff --git a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap index a57895e43..0504e7650 100644 --- a/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/postReply.spec.tsx.snap @@ -125,7 +125,7 @@ exports[`post a reply: open reply form 1`] = ` className="" >
- -
-
-
-
-
-
-
-
- -
-
- - - -
- -
+
+ + +
-
- - -
- +
+
+
+ +
+
+
+ + + +
+ +
+
+
+
+
+ + +
+
+ +
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -675,7 +683,7 @@ exports[`post a reply: optimistic response 1`] = ` className="" >
- -
-
-
-
-
-
-
-
- -
-
- - - -
-
Hello world!", + "__html": "Joining Too", } } - disabled={true} - id="comments-replyCommentForm-rte-comment-0" - onBlur={[Function]} - onChange={[Function]} - onCut={[Function]} - onFocus={[Function]} - onInput={[Function]} - onKeyDown={[Function]} - onPaste={[Function]} - onSelect={[Function]} /> +
+ + +
-
- - -
- +
+
+
+ +
+
+
+ + + +
+
Hello world!", + } + } + disabled={true} + id="comments-replyCommentForm-rte-comment-0" + onBlur={[Function]} + onChange={[Function]} + onCut={[Function]} + onFocus={[Function]} + onInput={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onSelect={[Function]} + /> +
+
+
+
+ + +
+
+ +
- - Markus -
- + Markus + +
+ +
+
+
+
-
- -
-
-
Hello world!", - } - } - /> -
- - + + +
@@ -1114,85 +1130,89 @@ exports[`post a reply: optimistic response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -1330,7 +1350,7 @@ exports[`post a reply: server response 1`] = ` className="" >
- + + +
@@ -1541,85 +1565,89 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
Hello world! (from server)", - } - } - /> -
- - + + +
@@ -1632,85 +1660,89 @@ exports[`post a reply: server response 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -1848,7 +1880,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -2055,85 +2091,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap index 078aeb205..5dd201997 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderReplies.spec.tsx.snap @@ -84,7 +84,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -271,85 +275,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -364,85 +372,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -457,85 +469,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Isabelle -
- + Isabelle + +
+ +
-
-
-
- - + + +
@@ -546,85 +562,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Isabelle -
- + Isabelle + +
+ +
-
-
-
- - + + +
@@ -637,85 +657,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Isabelle -
- + Isabelle + +
+ +
-
-
-
- - + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap index 232eeaee8..2a9640831 100644 --- a/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/renderStream.spec.tsx.snap @@ -84,7 +84,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -271,85 +275,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap index df74cf1da..dd986d07c 100644 --- a/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/showAllReplies.spec.tsx.snap @@ -84,7 +84,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -275,85 +279,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -476,7 +484,7 @@ exports[`show all replies 1`] = ` className="" >
- + + +
@@ -667,85 +679,89 @@ exports[`show all replies 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Lukas -
- + Lukas + +
+ +
-
-
-
- - + + +
@@ -756,85 +772,89 @@ exports[`show all replies 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Isabelle -
- + Isabelle + +
+ +
-
-
-
- - + + +
diff --git a/src/core/client/stream/test/comments/__snapshots__/showConversation.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/showConversation.spec.tsx.snap index 1b7acac67..22cc8c869 100644 --- a/src/core/client/stream/test/comments/__snapshots__/showConversation.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/showConversation.spec.tsx.snap @@ -84,7 +84,7 @@ exports[`renders comment stream 1`] = ` className="" >
- + + +
@@ -275,85 +279,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -368,85 +376,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -461,85 +473,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -554,85 +570,89 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
-
- - + + +
@@ -647,57 +667,93 @@ exports[`renders comment stream 1`] = ` className="HorizontalGutter-root HorizontalGutter-full" >
- - Markus -
- + Markus + +
+ +
-
-
@@ -802,108 +826,167 @@ exports[`shows conversation 1`] = ` role="tabpanel" >
- - Show All Comments -
+ + +
+
+

+ You are currently viewing a +

+

+ SINGLE CONVERSATION +

+ + View Full Discussion + +
+
- - Markus -
- +
+ + Markus + +
+ +
+
+
+
+
+
+ + +
+
-
-
-
- - -
-
+
diff --git a/src/core/client/stream/test/comments/permalinkView.spec.tsx b/src/core/client/stream/test/comments/permalinkView.spec.tsx index 2e0ee1225..10d5cbc15 100644 --- a/src/core/client/stream/test/comments/permalinkView.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkView.spec.tsx @@ -4,13 +4,29 @@ import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { createSinonStub } from "talk-framework/testHelpers"; -import { assets, comments, settings } from "../fixtures"; +import { assets, comments, commentWithReplies, settings } from "../fixtures"; import create from "./create"; let testRenderer: ReactTestRenderer; beforeEach(() => { const commentStub = { - ...comments[0], + ...commentWithReplies, + parentCount: 2, + parents: { + pageInfo: { + hasPreviousPage: false, + }, + edges: [ + { + node: comments[1], + cursor: comments[1].createdAt, + }, + { + node: comments[2], + cursor: comments[2].createdAt, + }, + ], + }, }; const assetStub = { @@ -68,7 +84,7 @@ it("show all comments", async () => { }; testRenderer.root .findByProps({ - id: "talk-comments-permalinkView-showAllComments", + id: "talk-comments-permalinkView-viewFullDiscussion", }) .props.onClick(mockEvent); await timeout(); diff --git a/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx b/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx index 8340ab39e..434eab212 100644 --- a/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx +++ b/src/core/client/stream/test/comments/permalinkViewCommentNotFound.spec.tsx @@ -66,7 +66,7 @@ it("show all comments", async () => { testRenderer.root .findByProps({ - id: "talk-comments-permalinkView-showAllComments", + id: "talk-comments-permalinkView-viewFullDiscussion", }) .props.onClick(mockEvent); await timeout(); diff --git a/src/core/client/stream/test/comments/permalinkViewLoadMoreParents.spec.tsx b/src/core/client/stream/test/comments/permalinkViewLoadMoreParents.spec.tsx new file mode 100644 index 000000000..0f50ce58c --- /dev/null +++ b/src/core/client/stream/test/comments/permalinkViewLoadMoreParents.spec.tsx @@ -0,0 +1,105 @@ +import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; + +import { timeout } from "talk-common/utils"; +import { createSinonStub } from "talk-framework/testHelpers"; + +import { assets, comments, settings } from "../fixtures"; +import create from "./create"; + +let testRenderer: ReactTestRenderer; +beforeEach(() => { + const commentStub = { + ...comments[0], + parentCount: 2, + rootParent: comments[2], + parents: createSinonStub( + s => s.throws(), + s => + s.withArgs({ last: 0 }).returns({ + pageInfo: { + startCursor: "0", + hasPreviousPage: true, + }, + edges: [], + }), + s => + s.withArgs({ last: 5, before: "0" }).returns({ + pageInfo: { + startCursor: "2", + hasPreviousPage: false, + }, + edges: [ + { + node: comments[1], + cursor: comments[1].createdAt, + }, + { + node: comments[2], + cursor: comments[2].createdAt, + }, + ], + }) + ), + }; + + const assetStub = { + ...assets[0], + comments: { + pageInfo: { + hasNextPage: false, + }, + edges: [ + { + node: commentStub, + cursor: commentStub.createdAt, + }, + ], + }, + }; + + const resolvers = { + Query: { + comment: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: commentStub.id }).returns(commentStub) + ), + asset: createSinonStub( + s => s.throws(), + s => + s + .withArgs(undefined, { id: assetStub.id, url: null }) + .returns(assetStub) + ), + settings: sinon.stub().returns(settings), + }, + }; + + ({ testRenderer } = create({ + // Set this to true, to see graphql responses. + logNetwork: false, + resolvers, + initLocalState: localRecord => { + localRecord.setValue(assetStub.id, "assetID"); + localRecord.setValue(commentStub.id, "commentID"); + }, + })); +}); + +it("renders permalink view", async () => { + // Wait for loading. + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("views pervious comments", async () => { + testRenderer.root + .find( + node => + node.props.onClick && + node.props.id === "comments-conversationThread-showHiddenComments" + ) + .props.onClick(); + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index 720b28c25..04c777810 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -1,3 +1,10 @@ +import { + denormalizeAsset, + denormalizeAssets, + denormalizeComment, + denormalizeComments, +} from "talk-framework/testHelpers"; + export const settings = { reaction: { icon: "thumb_up", @@ -38,7 +45,7 @@ export const baseComment = { }, }; -export const comments = [ +export const comments = denormalizeComments([ { ...baseComment, id: "comment-0", @@ -75,9 +82,9 @@ export const comments = [ author: users[2], body: "Comment Body 5", }, -]; +]); -export const commentWithReplies = { +export const commentWithReplies = denormalizeComment({ ...baseComment, id: "comment-with-replies", author: users[0], @@ -92,9 +99,9 @@ export const commentWithReplies = { }, }, replyCount: 2, -}; +}); -export const commentWithDeepReplies = { +export const commentWithDeepReplies = denormalizeComment({ ...baseComment, id: "comment-with-deep-replies", author: users[0], @@ -109,9 +116,9 @@ export const commentWithDeepReplies = { }, }, replyCount: 2, -}; +}); -export const commentWithDeepestReplies = { +export const commentWithDeepestReplies = denormalizeComment({ ...baseComment, id: "comment-with-deepest-replies", body: "body 0", @@ -205,7 +212,7 @@ export const commentWithDeepestReplies = { }, ], }, -}; +}); export const baseAsset = { isClosed: false, @@ -220,7 +227,7 @@ export const baseAsset = { }, }; -export const assets = [ +export const assets = denormalizeAssets([ { ...baseAsset, id: "asset-1", @@ -234,13 +241,10 @@ export const assets = [ hasNextPage: false, }, }, - commentCounts: { - totalVisible: 2, - }, }, -]; +]); -export const assetWithReplies = { +export const assetWithReplies = denormalizeAsset({ ...baseAsset, id: "asset-with-replies", url: "http://localhost/assets/asset-with-replies", @@ -253,12 +257,9 @@ export const assetWithReplies = { hasNextPage: false, }, }, - commentCounts: { - totalVisible: 2, - }, -}; +}); -export const assetWithDeepReplies = { +export const assetWithDeepReplies = denormalizeAsset({ ...baseAsset, id: "asset-with-deep-replies", url: "http://localhost/assets/asset-with-replies", @@ -274,12 +275,9 @@ export const assetWithDeepReplies = { hasNextPage: false, }, }, - commentCounts: { - totalVisible: 2, - }, -}; +}); -export const assetWithDeepestReplies = { +export const assetWithDeepestReplies = denormalizeAsset({ ...baseAsset, id: "asset-with-deepest-replies", url: "http://localhost/assets/asset-with-replies", @@ -294,7 +292,4 @@ export const assetWithDeepestReplies = { hasNextPage: false, }, }, - commentCounts: { - totalVisible: 1, - }, -}; +}); diff --git a/src/core/client/ui/components/HorizontalGutter/HorizontalGutter.tsx b/src/core/client/ui/components/HorizontalGutter/HorizontalGutter.tsx index 408dfb568..de93ba767 100644 --- a/src/core/client/ui/components/HorizontalGutter/HorizontalGutter.tsx +++ b/src/core/client/ui/components/HorizontalGutter/HorizontalGutter.tsx @@ -20,16 +20,37 @@ interface InnerProps extends HTMLAttributes { /** Internal: Forwarded Ref */ forwardRef?: Ref; + + /** + * The container used for the root node. + * Either a string to use a DOM element, a component, or an element. + * By default, it maps the variant to a good default headline component. + */ + container?: React.ReactElement | React.ComponentType | string; } const HorizontalGutter: StatelessComponent = props => { - const { classes, className, size, forwardRef, ...rest } = props; + const { classes, className, size, forwardRef, container, ...rest } = props; const rootClassName = cn(classes.root, className, classes[size!]); - return
; + + const innerProps = { + className: rootClassName, + ref: forwardRef, + ...rest, + }; + + const Container = container!; + + if (React.isValidElement(Container)) { + return React.cloneElement(Container, innerProps); + } else { + return ; + } }; HorizontalGutter.defaultProps = { size: "full", + container: "div", } as Partial; const enhanced = withForwardRef(withStyles(styles)(HorizontalGutter)); diff --git a/src/core/client/ui/shared/typography.css b/src/core/client/ui/shared/typography.css index 3012a3617..48e42ce69 100644 --- a/src/core/client/ui/shared/typography.css +++ b/src/core/client/ui/shared/typography.css @@ -181,3 +181,12 @@ letter-spacing: 0; color: var(--palette-grey-lighter); } + +.subTabCounter { + font-size: calc(14rem / var(--rem-base)); + font-weight: var(--font-weight-medium); + font-family: var(--font-family-sans-serif); + line-height: calc(18em / 18); + letter-spacing: calc(0.2em / 18); + color: var(--palette-text-primary); +} diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 09a6fe6f7..5bea357e0 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -917,12 +917,12 @@ type PageInfo { """ Indicates that there are more nodes after this subset. """ - hasNextPage: Boolean + hasNextPage: Boolean! """ Included for legacy Relay reasons. Always set to false. """ - hasPreviousPage: Boolean + hasPreviousPage: Boolean! """ Included for legacy Relay reasons. Always set to null. diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index 25840d96c..82ff21beb 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -316,7 +316,7 @@ export async function retrieveCommentParentsConnection( mongo: Db, tenantID: string, comment: Comment, - { last: limit, before: skip = -1 }: { last: number; before?: number } + { last: limit, before: skip = 0 }: { last: number; before?: number } ): Promise>>> { // Return nothing if this comment does not have any parents. if (!comment.parent_id) { @@ -334,26 +334,28 @@ export async function retrieveCommentParentsConnection( return createConnection({ pageInfo: { hasNextPage: false, - hasPreviousPage: false, + hasPreviousPage: !!comment.parent_id, + endCursor: 0, + startCursor: 0, }, }); } // If the last paramter is 1, and the after paramter is either unset or equal // to zero, then all we have to return is the direct parent. - if (limit === 1 && skip < 0) { + if (limit === 1 && skip <= 0) { const parent = await retrieveComment(mongo, tenantID, comment.parent_id); if (!parent) { throw new Error("parent comment not found"); } return { - edges: [{ node: parent, cursor: 0 }], + edges: [{ node: parent, cursor: 1 }], pageInfo: { hasNextPage: false, hasPreviousPage: comment.grandparent_ids.length > 0, - endCursor: 0, - startCursor: 0, + endCursor: 1, + startCursor: 1, }, }; } @@ -362,7 +364,7 @@ export async function retrieveCommentParentsConnection( const parentIDs = [comment.parent_id, ...comment.grandparent_ids.reverse()]; // Fetch the subset of the comment id's that we are going to query for. - const parentIDSubset = parentIDs.slice(skip + 1, skip + 1 + limit); + const parentIDSubset = parentIDs.slice(skip, skip + limit); // Retrieve the parents via the subset list. const parents = await retrieveManyComments(mongo, tenantID, parentIDSubset); diff --git a/src/core/server/models/connection.ts b/src/core/server/models/connection.ts index 3eeacd937..f58a850e7 100644 --- a/src/core/server/models/connection.ts +++ b/src/core/server/models/connection.ts @@ -32,7 +32,10 @@ export function createConnection( return merge( { edges: [], - pageInfo: {}, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + }, }, connection ); diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index c33aa9566..7797ad1b0 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -26,7 +26,7 @@ comments-replyList-showAll = Show All comments-permalinkButton-share = Share comments-permalinkPopover-copy = Copy comments-permalinkPopover-copied = Copied -comments-permalinkView-showAllComments = Show All Comments +comments-permalinkView-viewFullDiscussion = View Full Discussion comments-permalinkView-commentNotFound = Comment not found comments-rte-bold = @@ -72,6 +72,14 @@ comments-editCommentForm-editRemainingTime = Edit: remaining comments-editCommentForm-editTimeExpired = Edit time has expired. You can no longer edit this comment. Why not post another one? comments-editedMarker-edited = Edited comments-showConversationLink-readMore = Read More of this Conversation > +comments-conversationThread-showHiddenComments = + { $count -> + [1] SHOW HIDDEN COMMENT + *[other] SHOW HIDDEN COMMENTS + } + +comments-permalinkView-currentViewing = You are currently viewing a +comments-permalinkView-singleConversation = SINGLE CONVERSATION ## Profile Tab profile-historyComment-viewConversation = View Conversation