Merge branch 'next' into next-respect

This commit is contained in:
Chi Vinh Le
2018-10-04 19:15:00 +02:00
102 changed files with 13134 additions and 9240 deletions
@@ -0,0 +1,14 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import ButtonsBar from "./ButtonsBar";
it("renders correctly", () => {
const props: PropTypesOf<typeof ButtonsBar> = {
children: "children",
};
const wrapper = shallow(<ButtonsBar {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -0,0 +1,13 @@
import React, { StatelessComponent } from "react";
import { Flex } from "talk-ui/components";
const ButtonsBar: StatelessComponent = props => {
return (
<Flex direction="row" itemGutter="half">
{props.children}
</Flex>
);
};
export default ButtonsBar;
@@ -2,7 +2,7 @@ import React, { StatelessComponent } from "react";
import HTMLContent from "talk-stream/components/HTMLContent";
import Timestamp from "talk-stream/components/Timestamp";
import { Flex } from "talk-ui/components";
import { Flex, HorizontalGutter } from "talk-ui/components";
import * as styles from "./Comment.css";
import EditedMarker from "./EditedMarker";
@@ -43,10 +43,10 @@ const Comment: StatelessComponent<CommentProps> = props => {
</TopBarLeft>
{props.topBarRight && <div>{props.topBarRight}</div>}
</Flex>
<HTMLContent>{props.body || ""}</HTMLContent>
<Flex className={styles.footer} direction="row" itemGutter="half">
<HorizontalGutter>
<HTMLContent>{props.body || ""}</HTMLContent>
{props.footer}
</Flex>
</HorizontalGutter>
</div>
);
};
@@ -0,0 +1,17 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import ShowConversationLink from "./ShowConversationLink";
it("renders correctly", () => {
const props: PropTypesOf<typeof ShowConversationLink> = {
id: "id",
onClick: noop,
href: "http://localhost/comment",
};
const wrapper = shallow(<ShowConversationLink {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -0,0 +1,33 @@
import React, { EventHandler, MouseEvent } from "react";
import { StatelessComponent } from "react";
import { Localized } from "fluent-react/compat";
import { Button } from "talk-ui/components";
export interface ShowConversationLinkProps {
id?: string;
href?: string;
onClick?: EventHandler<MouseEvent>;
}
const ShowConversationLink: StatelessComponent<
ShowConversationLinkProps
> = props => {
return (
<Localized id="comments-showConversationLink-readMore">
<Button
id={props.id}
variant="underlined"
color="primary"
href={props.href}
onClick={props.onClick}
target="_parent"
anchor
>
Read More of this Conversation >
</Button>
</Localized>
);
};
export default ShowConversationLink;
@@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<withPropsOnChange(Flex)
direction="row"
itemGutter="half"
>
children
</withPropsOnChange(Flex)>
`;
@@ -30,15 +30,11 @@ exports[`renders username and body 1`] = `
topBarRight
</div>
</withPropsOnChange(Flex)>
<HTMLContent>
Woof
</HTMLContent>
<withPropsOnChange(Flex)
className="Comment-footer"
direction="row"
itemGutter="half"
>
<withPropsOnChange(HorizontalGutter)>
<HTMLContent>
Woof
</HTMLContent>
footer
</withPropsOnChange(Flex)>
</withPropsOnChange(HorizontalGutter)>
</div>
`;
@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<Localized
id="comments-showConversationLink-readMore"
>
<withPropsOnChange(Button)
anchor={true}
color="primary"
href="http://localhost/comment"
id="id"
onClick={[Function]}
target="_parent"
variant="underlined"
>
Read More of this Conversation &gt;
</withPropsOnChange(Button)>
</Localized>
`;
@@ -1,3 +1,5 @@
export { default, default as IndentedComment } from "./IndentedComment";
export { default as TopBarLeft } from "./TopBarLeft";
export { default as Username } from "./Username";
export { default as ButtonsBar } from "./ButtonsBar";
export { default as ShowConversationLink } from "./ShowConversationLink";
@@ -14,7 +14,10 @@ it("renders correctly", () => {
const props: PropTypesOf<typeof ReplyListN> = {
asset: { id: "asset-id" },
comment: { id: "comment-id" },
comments: [{ id: "comment-1" }, { id: "comment-2" }],
comments: [
{ id: "comment-1" },
{ id: "comment-2", showConversationLink: true },
],
onShowAll: noop,
hasMore: false,
disableShowAll: false,
@@ -15,9 +15,11 @@ export interface ReplyListProps {
id: string;
};
comments: ReadonlyArray<
{ id: string; replyListElement?: React.ReactElement<any> } & PropTypesOf<
typeof CommentContainer
>["comment"]
{
id: string;
replyListElement?: React.ReactElement<any>;
showConversationLink?: boolean;
} & PropTypesOf<typeof CommentContainer>["comment"]
>;
settings: PropTypesOf<typeof CommentContainer>["settings"];
onShowAll?: () => void;
@@ -45,6 +47,7 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
indentLevel={props.indentLevel}
localReply={props.localReply}
disableReplies={props.disableReplies}
showConversationLink={!!comment.showConversationLink}
/>
{comment.replyListElement}
</HorizontalGutter>
@@ -8,7 +8,7 @@ exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -32,12 +32,13 @@ exports[`renders correctly 1`] = `
},
}
}
showConversationLink={false}
/>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -46,6 +47,7 @@ exports[`renders correctly 1`] = `
comment={
Object {
"id": "comment-2",
"showConversationLink": true,
}
}
disableReplies={false}
@@ -61,6 +63,7 @@ exports[`renders correctly 1`] = `
},
}
}
showConversationLink={true}
/>
</withPropsOnChange(HorizontalGutter)>
</withPropsOnChange(HorizontalGutter)>
@@ -74,7 +77,7 @@ exports[`when there is more disables load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -96,12 +99,13 @@ exports[`when there is more disables load more button 1`] = `
},
}
}
showConversationLink={false}
/>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -123,6 +127,7 @@ exports[`when there is more disables load more button 1`] = `
},
}
}
showConversationLink={false}
/>
</withPropsOnChange(HorizontalGutter)>
<Indent
@@ -155,7 +160,7 @@ exports[`when there is more renders a load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -177,12 +182,13 @@ exports[`when there is more renders a load more button 1`] = `
},
}
}
showConversationLink={false}
/>
</withPropsOnChange(HorizontalGutter)>
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -204,6 +210,7 @@ exports[`when there is more renders a load more button 1`] = `
},
}
}
showConversationLink={false}
/>
</withPropsOnChange(HorizontalGutter)>
<Indent
@@ -21,7 +21,7 @@ exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -69,7 +69,7 @@ exports[`renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -139,7 +139,7 @@ exports[`when there is more disables load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -187,7 +187,7 @@ exports[`when there is more disables load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -271,7 +271,7 @@ exports[`when there is more renders a load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -319,7 +319,7 @@ exports[`when there is more renders a load more button 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -405,7 +405,7 @@ exports[`when use is logged in renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-1"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -453,7 +453,7 @@ exports[`when use is logged in renders correctly 1`] = `
<withPropsOnChange(HorizontalGutter)
key="comment-2"
>
<withContext(createMutationContainer(Relay(CommentContainer)))
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(CommentContainer)))))
asset={
Object {
"id": "asset-id",
@@ -14,7 +14,7 @@ it("renders username and body", () => {
const props: PropTypesOf<typeof CommentContainerN> = {
me: null,
asset: {
id: "asset-id",
url: "http://localhost/asset",
},
comment: {
id: "comment-id",
@@ -38,6 +38,7 @@ it("renders username and body", () => {
},
indentLevel: 1,
showAuthPopup: noop as any,
setCommentID: noop as any,
localReply: false,
disableReplies: false,
};
@@ -50,7 +51,7 @@ it("renders body only", () => {
const props: PropTypesOf<typeof CommentContainerN> = {
me: null,
asset: {
id: "asset-id",
url: "http://localhost/asset",
},
comment: {
id: "comment-id",
@@ -74,6 +75,7 @@ it("renders body only", () => {
},
indentLevel: 1,
showAuthPopup: noop as any,
setCommentID: noop as any,
};
const wrapper = shallow(<CommentContainerN {...props} />);
@@ -84,7 +86,7 @@ it("hide reply button", () => {
const props: PropTypesOf<typeof CommentContainerN> = {
me: null,
asset: {
id: "asset-id",
url: "http://localhost/asset",
},
comment: {
id: "comment-id",
@@ -108,6 +110,7 @@ it("hide reply button", () => {
},
indentLevel: 1,
showAuthPopup: noop as any,
setCommentID: noop as any,
localReply: false,
disableReplies: true,
};
@@ -115,3 +118,35 @@ it("hide reply button", () => {
const wrapper = shallow(<CommentContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("shows conversation link", () => {
const props: PropTypesOf<typeof CommentContainerN> = {
me: null,
asset: {
url: "http://localhost/asset",
},
comment: {
id: "comment-id",
author: {
id: "author-id",
username: "Marvin",
},
body: "Woof",
createdAt: "1995-12-17T03:24:00.000Z",
editing: {
edited: false,
editableUntil: "1995-12-17T03:24:30.000Z",
},
pending: false,
},
indentLevel: 1,
showAuthPopup: noop as any,
setCommentID: noop as any,
localReply: false,
disableReplies: false,
showConversationLink: true,
};
const wrapper = shallow(<CommentContainerN {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -1,8 +1,9 @@
import { Localized } from "fluent-react/compat";
import React, { Component } from "react";
import React, { Component, MouseEvent } from "react";
import { graphql } from "react-relay";
import { isBeforeDate } from "talk-common/utils";
import { getURLWithCommentID } from "talk-framework/helpers";
import withFragmentContainer from "talk-framework/lib/relay/withFragmentContainer";
import { PropTypesOf } from "talk-framework/types";
import { CommentContainer_asset as AssetData } from "talk-stream/__generated__/CommentContainer_asset.graphql";
@@ -10,13 +11,18 @@ import { CommentContainer_comment as CommentData } from "talk-stream/__generated
import { CommentContainer_me as MeData } from "talk-stream/__generated__/CommentContainer_me.graphql";
import { CommentContainer_settings as SettingsData } from "talk-stream/__generated__/CommentContainer_settings.graphql";
import {
SetCommentIDMutation,
ShowAuthPopupMutation,
withSetCommentIDMutation,
withShowAuthPopupMutation,
} from "talk-stream/mutations";
import ReactionButtonContainer from "talk-stream/tabs/comments/containers/ReactionButtonContainer";
import { Button } from "talk-ui/components";
import Comment from "../components/Comment";
import Comment, {
ButtonsBar,
ShowConversationLink,
} from "../components/Comment";
import ReplyButton from "../components/Comment/ReplyButton";
import EditCommentFormContainer from "./EditCommentFormContainer";
import PermalinkButtonContainer from "./PermalinkButtonContainer";
@@ -29,6 +35,7 @@ interface InnerProps {
settings: SettingsData;
indentLevel?: number;
showAuthPopup: ShowAuthPopupMutation;
setCommentID: SetCommentIDMutation;
/**
* localReply will integrate the mutation response into
* localReplies
@@ -36,6 +43,8 @@ interface InnerProps {
localReply?: boolean;
/** disableReplies will remove the ReplyButton */
disableReplies?: boolean;
/** showConversationLink will render a link to the conversation */
showConversationLink?: boolean;
}
interface State {
@@ -116,6 +125,12 @@ export class CommentContainer extends Component<InnerProps, State> {
return;
}
private handleShowConversation = (e: MouseEvent) => {
e.preventDefault();
this.props.setCommentID({ id: this.props.comment.id });
return false;
};
public render() {
const {
comment,
@@ -124,6 +139,7 @@ export class CommentContainer extends Component<InnerProps, State> {
indentLevel,
localReply,
disableReplies,
showConversationLink,
} = this.props;
const { showReplyDialog, showEditDialog, editable } = this.state;
if (showEditDialog) {
@@ -161,18 +177,32 @@ export class CommentContainer extends Component<InnerProps, State> {
}
footer={
<>
{!disableReplies && (
<ReplyButton
id={`comments-commentContainer-replyButton-${comment.id}`}
onClick={this.openReplyDialog}
active={showReplyDialog}
/>
)}
<PermalinkButtonContainer commentID={comment.id} />
{this.props.me && (
<ReactionButtonContainer
comment={comment}
settings={settings}
<ButtonsBar>
{!disableReplies && (
<ReplyButton
id={`comments-commentContainer-replyButton-${comment.id}`}
onClick={this.openReplyDialog}
active={showReplyDialog}
/>
)}
<PermalinkButtonContainer commentID={comment.id} />
{this.props.me && (
<ReactionButtonContainer
comment={comment}
settings={settings}
/>
)}
</ButtonsBar>
{showConversationLink && (
<ShowConversationLink
id={`comments-commentContainer-showConversation-${
comment.id
}`}
onClick={this.handleShowConversation}
href={getURLWithCommentID(
this.props.asset.url,
this.props.comment.id
)}
/>
)}
</>
@@ -191,43 +221,46 @@ export class CommentContainer extends Component<InnerProps, State> {
}
}
const enhanced = withShowAuthPopupMutation(
withFragmentContainer<InnerProps>({
me: graphql`
fragment CommentContainer_me on User {
id
}
`,
asset: graphql`
fragment CommentContainer_asset on Asset {
...ReplyCommentFormContainer_asset
}
`,
comment: graphql`
fragment CommentContainer_comment on Comment {
id
author {
const enhanced = withSetCommentIDMutation(
withShowAuthPopupMutation(
withFragmentContainer<InnerProps>({
me: graphql`
fragment CommentContainer_me on User {
id
username
}
body
createdAt
editing {
edited
editableUntil
`,
asset: graphql`
fragment CommentContainer_asset on Asset {
url
...ReplyCommentFormContainer_asset
}
pending
...ReplyCommentFormContainer_comment
...EditCommentFormContainer_comment
...ReactionButtonContainer_comment
}
`,
settings: graphql`
fragment CommentContainer_settings on Settings {
...ReactionButtonContainer_settings
}
`,
})(CommentContainer)
`,
comment: graphql`
fragment CommentContainer_comment on Comment {
id
author {
id
username
}
body
createdAt
editing {
edited
editableUntil
}
pending
...ReplyCommentFormContainer_comment
...EditCommentFormContainer_comment
...ReactionButtonContainer_comment
}
`,
settings: graphql`
fragment CommentContainer_settings on Settings {
...ReactionButtonContainer_settings
}
`,
})(CommentContainer)
)
);
export type CommentContainerProps = PropTypesOf<typeof enhanced>;
@@ -1,7 +1,7 @@
import React, { StatelessComponent } from "react";
import { graphql } from "react-relay";
import { getURLWithCommentID } from "talk-framework/helpers";
import { withLocalStateContainer } from "talk-framework/lib/relay";
import { modifyQuery } from "talk-framework/utils";
import { PermalinkButtonContainerLocal as Local } from "talk-stream/__generated__/PermalinkButtonContainerLocal.graphql";
import PermalinkButton from "../components/PermalinkButton";
@@ -18,7 +18,7 @@ export const PermalinkContainer: StatelessComponent<InnerProps> = ({
return local.assetURL ? (
<PermalinkButton
commentID={commentID}
url={modifyQuery(local.assetURL, { commentID })}
url={getURLWithCommentID(local.assetURL, commentID)}
/>
) : null;
};
@@ -1,10 +1,10 @@
import { Child as PymChild } from "pym.js";
import qs from "query-string";
import React, { MouseEvent } from "react";
import { graphql } from "react-relay";
import { getURLWithCommentID } from "talk-framework/helpers";
import { withContext } from "talk-framework/lib/bootstrap";
import { withFragmentContainer } from "talk-framework/lib/relay";
import { buildURL, parseURL } from "talk-framework/utils";
import { PermalinkViewContainer_asset as AssetData } from "talk-stream/__generated__/PermalinkViewContainer_asset.graphql";
import { PermalinkViewContainer_comment as CommentData } from "talk-stream/__generated__/PermalinkViewContainer_comment.graphql";
import { PermalinkViewContainer_me as MeData } from "talk-stream/__generated__/PermalinkViewContainer_me.graphql";
@@ -34,13 +34,8 @@ class PermalinkViewContainer extends React.Component<
};
private getShowAllCommentsHref() {
const { pym } = this.props;
const urlParts = parseURL((pym && pym.parentUrl) || window.location.href);
const search = qs.stringify({
...qs.parse(urlParts.search),
commentID: undefined,
});
// Remove the commentId url param.
return buildURL({ ...urlParts, search });
const url = (pym && pym.parentUrl) || window.location.href;
return getURLWithCommentID(url, undefined);
}
public componentDidMount() {
@@ -12,12 +12,16 @@ import {
COMMENT_SORT,
ReplyListContainer1PaginationQueryVariables,
} from "talk-stream/__generated__/ReplyListContainer1PaginationQuery.graphql";
import { ReplyListContainer5_comment as Comment5Data } from "talk-stream/__generated__/ReplyListContainer5_comment.graphql";
import { StatelessComponent } from "enzyme";
import { FragmentKeys } from "talk-framework/lib/relay/types";
import ReplyList from "../components/ReplyList";
import LocalReplyListContainer from "./LocalReplyListContainer";
type UnpackArray<T> = T extends ReadonlyArray<infer U> ? U : any;
type ReplyNode5 = UnpackArray<Comment5Data["replies"]["edges"]>["node"];
export interface BaseProps {
me: MeData | null;
asset: AssetData;
@@ -62,6 +66,8 @@ export class ReplyListContainer extends React.Component<InnerProps> {
settings={this.props.settings}
/>
),
// ReplyListContainer5 contains replyCount.
showConversationLink: ((edge.node as any) as ReplyNode5).replyCount > 0,
}));
return (
<ReplyList
@@ -181,6 +187,7 @@ const ReplyListContainer5 = createReplyListContainer(
edges {
node {
id
replyCount
...CommentContainer_comment
...LocalReplyListContainer_comment
}
@@ -14,9 +14,11 @@ exports[`hide reply button 1`] = `
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
<ButtonsBar>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
@@ -40,14 +42,16 @@ exports[`renders body only 1`] = `
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
@@ -71,14 +75,54 @@ exports[`renders username and body 1`] = `
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
</ButtonsBar>
</React.Fragment>
}
id="comment-comment-id"
indentLevel={1}
showEditedMarker={false}
/>
</React.Fragment>
`;
exports[`shows conversation link 1`] = `
<React.Fragment>
<IndentedComment
author={
Object {
"id": "author-id",
"username": "Marvin",
}
}
blur={false}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
footer={
<React.Fragment>
<ButtonsBar>
<ReplyButton
active={false}
id="comments-commentContainer-replyButton-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
</ButtonsBar>
<ShowConversationLink
href="http://localhost/asset?commentID=comment-id"
id="comments-commentContainer-showConversation-comment-id"
onClick={[Function]}
/>
<withContext(withLocalStateContainer(PermalinkContainer))
commentID="comment-id"
/>
</React.Fragment>
}
id="comment-comment-id"
@@ -51,6 +51,7 @@ exports[`renders correctly 1`] = `
}
}
/>,
"showConversationLink": false,
},
Object {
"id": "comment-2",
@@ -75,6 +76,7 @@ exports[`renders correctly 1`] = `
}
}
/>,
"showConversationLink": false,
},
]
}
@@ -127,10 +129,12 @@ exports[`when has more replies renders hasMore 1`] = `
Object {
"id": "comment-1",
"replyListElement": undefined,
"showConversationLink": false,
},
Object {
"id": "comment-2",
"replyListElement": undefined,
"showConversationLink": false,
},
]
}
@@ -182,10 +186,12 @@ exports[`when has more replies when showing all disables show all button 1`] = `
Object {
"id": "comment-1",
"replyListElement": undefined,
"showConversationLink": false,
},
Object {
"id": "comment-2",
"replyListElement": undefined,
"showConversationLink": false,
},
]
}
@@ -237,10 +243,12 @@ exports[`when has more replies when showing all enable show all button after loa
Object {
"id": "comment-1",
"replyListElement": undefined,
"showConversationLink": false,
},
Object {
"id": "comment-2",
"replyListElement": undefined,
"showConversationLink": false,
},
]
}
@@ -0,0 +1,36 @@
import { Localized } from "fluent-react/compat";
import * as React from "react";
import { StatelessComponent } from "react";
import { HorizontalGutter, Typography } from "talk-ui/components";
import HistoryComment from "./HistoryComment";
interface Comment {
id: string;
body: string | null;
createdAt: any;
replyCount: number | null;
asset: {
title: string | null;
};
conversationURL: string;
onGotoConversation: (e: React.MouseEvent) => void;
}
interface CommentsHistoryProps {
comments: Comment[];
}
const CommentsHistory: StatelessComponent<CommentsHistoryProps> = props => {
return (
<HorizontalGutter>
<Localized id="profile-historyComment-commentHistory">
<Typography variant="heading3">Comment History</Typography>
</Localized>
{props.comments.map(comment => (
<HistoryComment key={comment.id} {...comment} />
))}
</HorizontalGutter>
);
};
export default CommentsHistory;
@@ -0,0 +1,18 @@
.icon {
color: var(--palette-text-secondary);
}
.button,
.replies,
.story {
composes: button from "talk-ui/shared/typography.css";
}
.sideBar {
min-width: 180px;
padding-left: var(--spacing-unit);
}
.body {
word-break: break-all;
}
@@ -0,0 +1,79 @@
import { Localized } from "fluent-react/compat";
import * as React from "react";
import { StatelessComponent } from "react";
import Timestamp from "talk-stream/components/Timestamp";
import {
Button,
Flex,
HorizontalGutter,
Icon,
Typography,
} from "talk-ui/components";
import HTMLContent from "../../../components/HTMLContent";
import * as styles from "./HistoryComment.css";
export interface HistoryCommentProps {
body: string | null;
createdAt: string;
replyCount: number | null;
asset: {
title: string | null;
};
conversationURL: string;
onGotoConversation: (e: React.MouseEvent) => void;
}
const HistoryComment: StatelessComponent<HistoryCommentProps> = props => {
return (
<HorizontalGutter>
<Flex direction="row" justifyContent="space-between">
<Typography variant="bodyCopy" container="div">
{props.body && (
<HTMLContent className={styles.body}>{props.body}</HTMLContent>
)}
</Typography>
<Flex className={styles.sideBar} direction="column">
<Flex direction="row" alignItems="center" itemGutter="half">
<Button
variant="underlined"
target="_parent"
href={props.conversationURL}
onClick={props.onGotoConversation}
anchor
>
<Icon>launch</Icon>
<Localized id="profile-historyComment-viewConversation">
<span>View Conversation</span>
</Localized>
</Button>
</Flex>
<Flex direction="row" alignItems="center" itemGutter="half">
<Icon className={styles.icon}>schedule</Icon>
<Timestamp>{props.createdAt}</Timestamp>
</Flex>
</Flex>
</Flex>
{!!props.replyCount && (
<Flex
direction="row"
alignItems="center"
itemGutter="half"
className={styles.replies}
>
<Icon className={styles.icon}>reply</Icon>
<Localized
id="profile-historyComment-replies"
$replyCount={props.replyCount}
>
<span>{"Replies {$replyCount}"}</span>
</Localized>
</Flex>
)}
<Localized id="profile-historyComment-story" $title={props.asset.title}>
<span className={styles.story}>{"Story: {$title}"}</span>
</Localized>
</HorizontalGutter>
);
};
export default HistoryComment;
@@ -0,0 +1,25 @@
import * as React from "react";
import { StatelessComponent } from "react";
import { PropTypesOf } from "talk-framework/types";
import UserBoxContainer from "talk-stream/containers/UserBoxContainer";
import { HorizontalGutter } from "talk-ui/components";
import CommentsHistoryContainer from "../containers/CommentsHistoryContainer";
export interface ProfileProps {
asset: PropTypesOf<typeof CommentsHistoryContainer>["asset"];
me: PropTypesOf<typeof UserBoxContainer>["me"] &
PropTypesOf<typeof CommentsHistoryContainer>["me"];
}
const Profile: StatelessComponent<ProfileProps> = props => {
return (
<HorizontalGutter size="double">
<UserBoxContainer me={props.me} />
{props.me && (
<CommentsHistoryContainer me={props.me} asset={props.asset} />
)}
</HorizontalGutter>
);
};
export default Profile;
@@ -0,0 +1,70 @@
import React from "react";
import { graphql } from "react-relay";
import { getURLWithCommentID } from "talk-framework/helpers";
import { withFragmentContainer } from "talk-framework/lib/relay";
import { CommentsHistoryContainer_asset as AssetData } from "talk-stream/__generated__/CommentsHistoryContainer_asset.graphql";
import { CommentsHistoryContainer_me as CommentsData } from "talk-stream/__generated__/CommentsHistoryContainer_me.graphql";
import {
SetCommentIDMutation,
withSetCommentIDMutation,
} from "talk-stream/mutations";
import CommentsHistory from "../components/CommentsHistory";
interface CommentsHistoryContainerProps {
setCommentID: SetCommentIDMutation;
me: CommentsData;
asset: AssetData;
}
export class CommentsHistoryContainer extends React.Component<
CommentsHistoryContainerProps
> {
private onGoToConversation = (id: string) => {
this.props.setCommentID({ id });
};
public render() {
const comments = this.props.me.comments.edges.map(edge => ({
...edge.node,
conversationURL: getURLWithCommentID(edge.node.asset.url, edge.node.id),
onGotoConversation: (e: React.MouseEvent) => {
if (this.props.asset.id === edge.node.asset.id) {
this.onGoToConversation(edge.node.id);
e.preventDefault();
}
},
}));
return <CommentsHistory comments={comments} />;
}
}
const enhanced = withSetCommentIDMutation(
withFragmentContainer<CommentsHistoryContainerProps>({
asset: graphql`
fragment CommentsHistoryContainer_asset on Asset {
id
}
`,
me: graphql`
fragment CommentsHistoryContainer_me on User {
comments {
edges {
node {
id
body
createdAt
replyCount
asset {
id
title
url
}
}
}
}
}
`,
})(CommentsHistoryContainer)
);
export default enhanced;
@@ -0,0 +1,34 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "talk-framework/lib/relay";
import { ProfileContainer_asset as AssetData } from "talk-stream/__generated__/ProfileContainer_asset.graphql";
import { ProfileContainer_me as MeData } from "talk-stream/__generated__/ProfileContainer_me.graphql";
import Profile from "../components/Profile";
interface ProfileContainerProps {
me: MeData;
asset: AssetData;
}
export class StreamContainer extends React.Component<ProfileContainerProps> {
public render() {
return <Profile me={this.props.me} asset={this.props.asset} />;
}
}
const enhanced = withFragmentContainer<ProfileContainerProps>({
asset: graphql`
fragment ProfileContainer_asset on Asset {
...CommentsHistoryContainer_asset
}
`,
me: graphql`
fragment ProfileContainer_me on User {
...UserBoxContainer_me
...CommentsHistoryContainer_me
}
`,
})(StreamContainer);
export default enhanced;
@@ -0,0 +1,80 @@
import { Localized } from "fluent-react/compat";
import React, { StatelessComponent } from "react";
import { ReadyState } from "react-relay";
import {
graphql,
QueryRenderer,
withLocalStateContainer,
} from "talk-framework/lib/relay";
import { ProfileQuery as QueryTypes } from "talk-stream/__generated__/ProfileQuery.graphql";
import { ProfileQueryLocal as Local } from "talk-stream/__generated__/ProfileQueryLocal.graphql";
import { Spinner } from "talk-ui/components";
import ProfileContainer from "../containers/ProfileContainer";
interface InnerProps {
local: Local;
}
export const render = ({
error,
props,
}: ReadyState<QueryTypes["response"]>) => {
if (error) {
return <div>{error.message}</div>;
}
if (props) {
if (!props.me) {
return (
<Localized id="profile-profileQuery-errorLoadingProfile">
<div>Error loading profile</div>
</Localized>
);
}
if (!props.asset) {
return (
<Localized id="comments-profileQuery-assetNotFound">
<div>Asset not found</div>
</Localized>
);
}
return <ProfileContainer me={props.me} asset={props.asset} />;
}
return <Spinner />;
};
const ProfileQuery: StatelessComponent<InnerProps> = ({
local: { assetID, assetURL },
}) => (
<QueryRenderer<QueryTypes>
query={graphql`
query ProfileQuery($assetID: ID, $assetURL: String) {
asset(id: $assetID, url: $assetURL) {
...ProfileContainer_asset
}
me {
...ProfileContainer_me
}
}
`}
variables={{
assetID,
assetURL,
}}
render={render}
/>
);
const enhanced = withLocalStateContainer(
graphql`
fragment ProfileQueryLocal on Local {
assetID
assetURL
}
`
)(ProfileQuery);
export default enhanced;