Merge branch 'next' into profile-mobile

This commit is contained in:
Wyatt Johnson
2018-10-16 18:23:28 +00:00
committed by GitHub
188 changed files with 12902 additions and 6343 deletions
@@ -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));
}
@@ -8,9 +8,7 @@ import Comment from "./Comment";
it("renders username and body", () => {
const props: PropTypesOf<typeof Comment> = {
id: "comment-id",
author: {
username: "Marvin",
},
username: "Marvin",
body: "Woof",
createdAt: "1995-12-17T03:24:00.000Z",
topBarRight: "topBarRight",
@@ -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<CommentProps> = props => {
return (
<div role="article" className={styles.root}>
<div
role="article"
className={cn(styles.root, { [styles.highlight]: props.highlight })}
>
<Flex
className={styles.topBar}
direction="row"
@@ -32,10 +35,7 @@ const Comment: StatelessComponent<CommentProps> = props => {
id={props.id}
>
<TopBarLeft>
{props.author &&
props.author.username && (
<Username>{props.author.username}</Username>
)}
{props.username && <Username>{props.username}</Username>}
<Flex direction="row" alignItems="baseline" itemGutter>
<Timestamp>{props.createdAt}</Timestamp>
{props.showEditedMarker && <EditedMarker />}
@@ -8,9 +8,7 @@ import IndentedComment from "./IndentedComment";
it("renders correctly", () => {
const props: PropTypesOf<typeof IndentedComment> = {
indentLevel: 1,
author: {
username: "Marvin",
},
username: "Marvin",
body: "Woof",
createdAt: "1995-12-17T03:24:00.000Z",
};
@@ -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<RootParentProps> = props => {
return (
<Flex direction="row" justifyContent="space-between" id={props.id}>
<TopBarLeft>
{props.username && <Username>{props.username}</Username>}
<Flex direction="row" alignItems="baseline" itemGutter>
<Timestamp>{props.createdAt}</Timestamp>
</Flex>
</TopBarLeft>
</Flex>
);
};
export default RootParent;
@@ -6,13 +6,9 @@ exports[`renders correctly 1`] = `
level={1}
>
<Comment
author={
Object {
"username": "Marvin",
}
}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
username="Marvin"
/>
</Indent>
`;
@@ -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";
@@ -0,0 +1,7 @@
.root {
}
.loadMore {
padding-left: var(--spacing-unit);
padding-bottom: var(--spacing-unit);
}
@@ -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<typeof ConversationThreadN> = {
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(<ConversationThreadN {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with disabled load more", () => {
const props: PropTypesOf<typeof ConversationThreadN> = {
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(<ConversationThreadN {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
it("renders with no parent comments", () => {
const props: PropTypesOf<typeof ConversationThreadN> = {
className: "root",
me: {},
asset: {},
settings: {},
comment: {},
disableLoadMore: false,
loadMore: noop,
remaining: 0,
parents: [],
rootParent: null,
};
const wrapper = shallow(<ConversationThreadN {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -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<typeof CommentContainer>["me"] &
(PropTypesOf<typeof LocalReplyListContainer>["me"] | null);
asset: PropTypesOf<typeof CommentContainer>["asset"] &
PropTypesOf<typeof LocalReplyListContainer>["asset"];
settings: PropTypesOf<typeof CommentContainer>["settings"] &
PropTypesOf<typeof LocalReplyListContainer>["settings"];
comment: PropTypesOf<typeof CommentContainer>["comment"];
disableLoadMore: boolean;
loadMore: () => void;
remaining: number;
parents: Array<
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"] &
PropTypesOf<typeof LocalReplyListContainer>["comment"]
>;
rootParent: {
id: string;
createdAt: string;
username: string | null;
} | null;
}
const ConversationThread: StatelessComponent<
ConversationThreadProps
> = props => {
if (props.remaining === 0 && props.parents.length === 0) {
return (
<div className={cn(props.className, styles.root)}>
<CommentContainer
comment={props.comment}
asset={props.asset}
settings={props.settings}
me={props.me}
highlight
/>
</div>
);
}
return (
<div className={cn(props.className, styles.root)}>
<HorizontalGutter container={<Line dotted />}>
{props.rootParent && (
<Circle>
<RootParent
id={props.rootParent.id}
username={props.rootParent.username}
createdAt={props.rootParent.createdAt}
/>
</Circle>
)}
{props.remaining > 0 && (
<Circle hollow className={styles.loadMore}>
<Flex alignItems="center" itemGutter="half">
<Localized
id="comments-conversationThread-showHiddenComments"
$count={props.remaining}
>
<Button
onClick={props.loadMore}
disabled={props.disableLoadMore}
id="comments-conversationThread-showHiddenComments"
variant="underlined"
>
Show hidden comments
</Button>
</Localized>
{props.remaining > 1 && <Counter>{props.remaining}</Counter>}
</Flex>
</Circle>
)}
</HorizontalGutter>
<HorizontalGutter container={Line}>
{props.parents.map((parent, i) => (
<Circle key={parent.id} hollow={!!props.remaining || i > 0}>
<CommentContainer
comment={parent}
asset={props.asset}
me={props.me}
settings={props.settings}
localReply
/>
{props.me && (
<LocalReplyListContainer
asset={props.asset}
me={props.me}
settings={props.settings}
comment={parent}
indentLevel={1}
/>
)}
</Circle>
))}
<Circle end>
<CommentContainer
comment={props.comment}
asset={props.asset}
settings={props.settings}
me={props.me}
highlight
/>
</Circle>
</HorizontalGutter>
</div>
);
};
export default ConversationThread;
@@ -0,0 +1,8 @@
.root {
composes: subTabCounter from "talk-ui/shared/typography.css";
background-color: var(--palette-grey-dark);
color: var(--palette-text-light);
border-radius: 1px;
padding: 2px 3px 3px 3px;
}
@@ -0,0 +1,9 @@
import { shallow } from "enzyme";
import React from "react";
import Counter from "./Counter";
it("renders correctly", () => {
const wrapper = shallow(<Counter>20</Counter>);
expect(wrapper).toMatchSnapshot();
});
@@ -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> = props => {
return (
<span className={cn(styles.root, props.className)}>{props.children}</span>
);
};
export default Counter;
@@ -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);
}
@@ -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<typeof PermalinkViewN> = {
me: {},
asset: {},
settings: {},
comment: {},
showAllCommentsHref: "http://localhost/link",
onShowAllComments: noop,
};
const wrapper = shallow(<PermalinkViewN {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders comment not found", () => {
const props: PropTypesOf<typeof PermalinkViewN> = {
me: {},
asset: {},
settings: {},
comment: null,
showAllCommentsHref: "http://localhost/link",
onShowAllComments: noop,
};
const wrapper = shallow(<PermalinkViewN {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -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<typeof CommentContainer>["me"];
asset: PropTypesOf<typeof CommentContainer>["asset"];
settings: PropTypesOf<typeof CommentContainer>["settings"];
comment: PropTypesOf<typeof CommentContainer>["comment"] | null;
me: PropTypesOf<typeof ConversationThreadContainer>["me"] &
PropTypesOf<typeof ReplyListContainer>["me"] &
PropTypesOf<typeof UserBoxContainer>["me"];
asset: PropTypesOf<typeof ConversationThreadContainer>["asset"] &
PropTypesOf<typeof ReplyListContainer>["asset"];
comment:
| PropTypesOf<typeof ConversationThreadContainer>["comment"] &
PropTypesOf<typeof ReplyListContainer>["comment"]
| null;
settings: PropTypesOf<typeof ConversationThreadContainer>["settings"] &
PropTypesOf<typeof ReplyListContainer>["settings"];
showAllCommentsHref: string | null;
onShowAllComments: (e: MouseEvent<any>) => void;
}
@@ -25,38 +34,57 @@ const PermalinkView: StatelessComponent<PermalinkViewProps> = ({
me,
}) => {
return (
<div className={styles.root}>
{showAllCommentsHref && (
<Localized id="comments-permalinkView-showAllComments">
<Button
id="talk-comments-permalinkView-showAllComments"
variant="outlined"
color="primary"
onClick={onShowAllComments}
className={styles.button}
href={showAllCommentsHref}
target="_parent"
fullWidth
anchor
>
Show all Comments
</Button>
<HorizontalGutter className={styles.root} size="double">
<UserBoxContainer me={me} />
<Flex alignItems="center" justifyContent="center" direction="column">
<Localized id="comments-permalinkView-currentViewing">
<Typography className={styles.title1}>
You are currently viewing a
</Typography>
</Localized>
)}
<Localized id="comments-permalinkView-singleConversation">
<Typography className={styles.title2}>SINGLE CONVERSATION</Typography>
</Localized>
{showAllCommentsHref && (
<Localized id="comments-permalinkView-viewFullDiscussion">
<Button
id="talk-comments-permalinkView-viewFullDiscussion"
variant="underlined"
color="primary"
onClick={onShowAllComments}
href={showAllCommentsHref}
target="_parent"
anchor
>
View Full Discussion
</Button>
</Localized>
)}
</Flex>
{!comment && (
<Localized id="comments-permalinkView-commentNotFound">
<Typography>Comment not found</Typography>
</Localized>
)}
{comment && (
<CommentContainer
settings={settings}
me={me}
comment={comment}
asset={asset}
/>
<HorizontalGutter>
<ConversationThreadContainer
me={me}
comment={comment}
asset={asset}
settings={settings}
/>
<div className={styles.replyList}>
<ReplyListContainer
me={me}
comment={comment}
asset={asset}
settings={settings}
/>
</div>
</HorizontalGutter>
)}
</div>
</HorizontalGutter>
);
};
@@ -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));
@@ -95,6 +95,7 @@ const RTE: StatelessComponent<RTEProps> = props => {
className={className}
contentClassName={styles.content}
placeholderClassName={styles.placeholder}
toolbarClassName={styles.toolbar}
onChange={onChange}
value={value || defaultValue}
disabled={disabled}
@@ -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;
}
@@ -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<typeof Circle> = {
className: "root",
hollow: true,
end: true,
};
const wrapper = shallow(<Circle {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -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<CircleProps> = props => {
return (
<div className={cn(styles.circleContainer, props.className)}>
<div
className={cn(styles.circleSubContainer, { [styles.end]: props.end })}
>
<svg
className={styles.circle}
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
{props.hollow && (
<circle cx="50" cy="50" r="40" strokeWidth="8" fill="none" />
)}
{!props.hollow && <circle cx="50" cy="50" r="50" />}
</svg>
</div>
{props.children}
</div>
);
};
export default Circle;
@@ -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;
}
@@ -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<typeof Line> = {
className: "root",
dotted: true,
};
const wrapper = shallow(<Line {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -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<LineProps> = props => {
return (
<div
className={cn(styles.root, props.className, {
[styles.dotted]: props.dotted,
})}
>
{props.children}
</div>
);
};
export default Line;
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="Circle-circleContainer root"
>
<div
className="Circle-circleSubContainer Circle-end"
>
<svg
className="Circle-circle"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="50"
cy="50"
fill="none"
r="40"
strokeWidth="8"
/>
</svg>
</div>
</div>
`;
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="Line-root root Line-dotted"
/>
`;
@@ -0,0 +1,2 @@
export { default as Circle } from "./Circle";
export { default as Line } from "./Line";
@@ -0,0 +1,141 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders with no parent comments 1`] = `
<div
className="root ConversationThread-root"
>
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={Object {}}
comment={Object {}}
highlight={true}
me={Object {}}
settings={Object {}}
/>
</div>
`;
exports[`with 2 remaining parent comments renders correctly 1`] = `
<div
className="root ConversationThread-root"
>
<withPropsOnChange(HorizontalGutter)
container={
<Line
dotted={true}
/>
}
>
<Circle>
<RootParent
createdAt="1995-12-17T03:24:00.000Z"
id="root-parent"
username="parentAuthor"
/>
</Circle>
<Circle
className="ConversationThread-loadMore"
hollow={true}
>
<withPropsOnChange(Flex)
alignItems="center"
itemGutter="half"
>
<Localized
$count={2}
id="comments-conversationThread-showHiddenComments"
>
<withPropsOnChange(Button)
disabled={false}
id="comments-conversationThread-showHiddenComments"
onClick={[Function]}
variant="underlined"
>
Show hidden comments
</withPropsOnChange(Button)>
</Localized>
<Counter>
2
</Counter>
</withPropsOnChange(Flex)>
</Circle>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
container={[Function]}
>
<Circle
end={true}
>
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={Object {}}
comment={Object {}}
highlight={true}
me={Object {}}
settings={Object {}}
/>
</Circle>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
exports[`with 2 remaining parent comments renders with disabled load more 1`] = `
<div
className="root ConversationThread-root"
>
<withPropsOnChange(HorizontalGutter)
container={
<Line
dotted={true}
/>
}
>
<Circle>
<RootParent
createdAt="1995-12-17T03:24:00.000Z"
id="root-parent"
username="parentAuthor"
/>
</Circle>
<Circle
className="ConversationThread-loadMore"
hollow={true}
>
<withPropsOnChange(Flex)
alignItems="center"
itemGutter="half"
>
<Localized
$count={2}
id="comments-conversationThread-showHiddenComments"
>
<withPropsOnChange(Button)
disabled={true}
id="comments-conversationThread-showHiddenComments"
onClick={[Function]}
variant="underlined"
>
Show hidden comments
</withPropsOnChange(Button)>
</Localized>
<Counter>
2
</Counter>
</withPropsOnChange(Flex)>
</Circle>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
container={[Function]}
>
<Circle
end={true}
>
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={Object {}}
comment={Object {}}
highlight={true}
me={Object {}}
settings={Object {}}
/>
</Circle>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<span
className="Counter-root"
>
20
</span>
`;
@@ -0,0 +1,126 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders comment not found 1`] = `
<withPropsOnChange(HorizontalGutter)
className="PermalinkView-root"
size="double"
>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
me={Object {}}
/>
<withPropsOnChange(Flex)
alignItems="center"
direction="column"
justifyContent="center"
>
<Localized
id="comments-permalinkView-currentViewing"
>
<withPropsOnChange(Typography)
className="PermalinkView-title1"
>
You are currently viewing a
</withPropsOnChange(Typography)>
</Localized>
<Localized
id="comments-permalinkView-singleConversation"
>
<withPropsOnChange(Typography)
className="PermalinkView-title2"
>
SINGLE CONVERSATION
</withPropsOnChange(Typography)>
</Localized>
<Localized
id="comments-permalinkView-viewFullDiscussion"
>
<withPropsOnChange(Button)
anchor={true}
color="primary"
href="http://localhost/link"
id="talk-comments-permalinkView-viewFullDiscussion"
onClick={[Function]}
target="_parent"
variant="underlined"
>
View Full Discussion
</withPropsOnChange(Button)>
</Localized>
</withPropsOnChange(Flex)>
<Localized
id="comments-permalinkView-commentNotFound"
>
<withPropsOnChange(Typography)>
Comment not found
</withPropsOnChange(Typography)>
</Localized>
</withPropsOnChange(HorizontalGutter)>
`;
exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
className="PermalinkView-root"
size="double"
>
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
me={Object {}}
/>
<withPropsOnChange(Flex)
alignItems="center"
direction="column"
justifyContent="center"
>
<Localized
id="comments-permalinkView-currentViewing"
>
<withPropsOnChange(Typography)
className="PermalinkView-title1"
>
You are currently viewing a
</withPropsOnChange(Typography)>
</Localized>
<Localized
id="comments-permalinkView-singleConversation"
>
<withPropsOnChange(Typography)
className="PermalinkView-title2"
>
SINGLE CONVERSATION
</withPropsOnChange(Typography)>
</Localized>
<Localized
id="comments-permalinkView-viewFullDiscussion"
>
<withPropsOnChange(Button)
anchor={true}
color="primary"
href="http://localhost/link"
id="talk-comments-permalinkView-viewFullDiscussion"
onClick={[Function]}
target="_parent"
variant="underlined"
>
View Full Discussion
</withPropsOnChange(Button)>
</Localized>
</withPropsOnChange(Flex)>
<withPropsOnChange(HorizontalGutter)>
<withContext(withContext(createMutationContainer(Relay(ConversationThreadContainer))))
asset={Object {}}
comment={Object {}}
me={Object {}}
settings={Object {}}
/>
<div
className="PermalinkView-replyList"
>
<withProps(Relay(ReplyListContainer))
asset={Object {}}
comment={Object {}}
me={Object {}}
settings={Object {}}
/>
</div>
</withPropsOnChange(HorizontalGutter)>
</withPropsOnChange(HorizontalGutter)>
`;
@@ -62,7 +62,7 @@ exports[`renders correctly 1`] = `
placeholder="Post a comment"
placeholderClassName="RTE-placeholder"
placeholderClassNameDisabled=""
toolbarClassName=""
toolbarClassName="RTE-toolbar"
toolbarClassNameDisabled=""
value="Hello world"
/>
@@ -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<InnerProps, State> {
localReply,
disableReplies,
showConversationLink,
highlight,
me,
} = this.props;
const { showReplyDialog, showEditDialog, editable } = this.state;
@@ -152,15 +155,16 @@ export class CommentContainer extends Component<InnerProps, State> {
);
}
return (
<>
<HorizontalGutter>
<Comment
id={`comment-${comment.id}`}
indentLevel={indentLevel}
author={comment.author}
username={comment.author && comment.author.username}
body={comment.body}
createdAt={comment.createdAt}
blur={comment.pending || false}
showEditedMarker={comment.editing.edited}
highlight={highlight}
topBarRight={
(editable && (
<Localized id="comments-commentContainer-editButton">
@@ -216,7 +220,7 @@ export class CommentContainer extends Component<InnerProps, State> {
localReply={localReply}
/>
)}
</>
</HorizontalGutter>
);
}
}
@@ -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 (
<ConversationThread
me={me}
asset={asset}
comment={comment}
settings={settings}
parents={comment.parents.edges.map(edge => 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;
@@ -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<PermalinkViewContainerProps>({
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)
@@ -1,14 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`hide reply button 1`] = `
<React.Fragment>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
author={
Object {
"id": "author-id",
"username": "Marvin",
}
}
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
@@ -51,19 +45,14 @@ exports[`hide reply button 1`] = `
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
username="Marvin"
/>
</React.Fragment>
</withPropsOnChange(HorizontalGutter)>
`;
exports[`renders body only 1`] = `
<React.Fragment>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
author={
Object {
"id": "author-id",
"username": null,
}
}
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
@@ -111,19 +100,14 @@ exports[`renders body only 1`] = `
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
username={null}
/>
</React.Fragment>
</withPropsOnChange(HorizontalGutter)>
`;
exports[`renders username and body 1`] = `
<React.Fragment>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
author={
Object {
"id": "author-id",
"username": "Marvin",
}
}
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
@@ -171,19 +155,14 @@ exports[`renders username and body 1`] = `
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
username="Marvin"
/>
</React.Fragment>
</withPropsOnChange(HorizontalGutter)>
`;
exports[`shows conversation link 1`] = `
<React.Fragment>
<withPropsOnChange(HorizontalGutter)>
<IndentedComment
author={
Object {
"id": "author-id",
"username": "Marvin",
}
}
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
@@ -237,6 +216,7 @@ exports[`shows conversation link 1`] = `
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
username="Marvin"
/>
</React.Fragment>
</withPropsOnChange(HorizontalGutter)>
`;
@@ -64,6 +64,10 @@ const ProfileQuery: StatelessComponent<InnerProps> = ({
assetID,
assetURL,
}}
cacheConfig={{
// TODO: enable caching after mutations are adapted.
force: true,
}}
render={render}
/>
);