mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
Merge branch 'next' into next-server-threading
This commit is contained in:
@@ -30,6 +30,12 @@
|
||||
border-left: 3px solid var(--palette-grey-lighter);
|
||||
}
|
||||
|
||||
.level6 {
|
||||
padding-left: var(--spacing-unit);
|
||||
margin-left: calc(5 * var(--spacing-unit));
|
||||
border-left: 3px solid var(--palette-grey-lightest);
|
||||
}
|
||||
|
||||
.noBorder {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@@ -10,16 +10,28 @@ export interface IndentProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const levels = [
|
||||
"",
|
||||
styles.level1,
|
||||
styles.level2,
|
||||
styles.level3,
|
||||
styles.level4,
|
||||
styles.level5,
|
||||
styles.level6,
|
||||
];
|
||||
|
||||
function getLevelClassName(level: number = 0) {
|
||||
if (!(level in levels)) {
|
||||
throw new Error(`Indent level ${level} does not exist`);
|
||||
}
|
||||
return levels[level];
|
||||
}
|
||||
|
||||
const Indent: StatelessComponent<IndentProps> = props => {
|
||||
return (
|
||||
<div className={cn(props.className, styles.root)}>
|
||||
<div
|
||||
className={cn({
|
||||
[styles.level1]: props.level === 1,
|
||||
[styles.level2]: props.level === 2,
|
||||
[styles.level3]: props.level === 3,
|
||||
[styles.level4]: props.level === 4,
|
||||
[styles.level5]: props.level === 5,
|
||||
className={cn(getLevelClassName(props.level), {
|
||||
[styles.noBorder]: props.noBorder,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -20,6 +20,8 @@ it("renders correctly", () => {
|
||||
disableShowAll: false,
|
||||
indentLevel: 1,
|
||||
me: null,
|
||||
localReply: false,
|
||||
disableReplies: false,
|
||||
};
|
||||
const wrapper = shallow(<ReplyListN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -17,11 +17,13 @@ export interface ReplyListProps {
|
||||
comments: ReadonlyArray<
|
||||
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"]
|
||||
>;
|
||||
onShowAll: () => void;
|
||||
hasMore: boolean;
|
||||
disableShowAll: boolean;
|
||||
onShowAll?: () => void;
|
||||
hasMore?: boolean;
|
||||
disableShowAll?: boolean;
|
||||
indentLevel?: number;
|
||||
ReplyListComponent?: React.ComponentType<any>;
|
||||
localReply?: boolean;
|
||||
disableReplies?: boolean;
|
||||
}
|
||||
|
||||
function getReplyListElement(
|
||||
@@ -48,6 +50,8 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
|
||||
comment={comment}
|
||||
asset={props.asset}
|
||||
indentLevel={props.indentLevel}
|
||||
localReply={props.localReply}
|
||||
disableReplies={props.disableReplies}
|
||||
/>
|
||||
{getReplyListElement(props, comment)}
|
||||
</HorizontalGutter>
|
||||
|
||||
@@ -19,8 +19,10 @@ exports[`renders correctly 1`] = `
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
disableReplies={false}
|
||||
indentLevel={1}
|
||||
key="comment-1"
|
||||
localReply={false}
|
||||
me={null}
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
@@ -38,8 +40,10 @@ exports[`renders correctly 1`] = `
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
disableReplies={false}
|
||||
indentLevel={1}
|
||||
key="comment-2"
|
||||
localReply={false}
|
||||
me={null}
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
|
||||
@@ -32,6 +32,8 @@ it("renders username and body", () => {
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
localReply: false,
|
||||
disableReplies: false,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<CommentContainerN {...props} />);
|
||||
@@ -65,3 +67,33 @@ it("renders body only", () => {
|
||||
const wrapper = shallow(<CommentContainerN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("hide reply button", () => {
|
||||
const props: PropTypesOf<typeof CommentContainerN> = {
|
||||
me: null,
|
||||
asset: {
|
||||
id: "asset-id",
|
||||
},
|
||||
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,
|
||||
localReply: false,
|
||||
disableReplies: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<CommentContainerN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -26,6 +26,13 @@ interface InnerProps {
|
||||
asset: AssetData;
|
||||
indentLevel?: number;
|
||||
showAuthPopup: ShowAuthPopupMutation;
|
||||
/**
|
||||
* localReply will integrate the mutation response into
|
||||
* localReplies
|
||||
*/
|
||||
localReply?: boolean;
|
||||
/** disableReplies will remove the ReplyButton */
|
||||
disableReplies?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -107,7 +114,13 @@ export class CommentContainer extends Component<InnerProps, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { comment, asset, indentLevel } = this.props;
|
||||
const {
|
||||
comment,
|
||||
asset,
|
||||
indentLevel,
|
||||
localReply,
|
||||
disableReplies,
|
||||
} = this.props;
|
||||
const { showReplyDialog, showEditDialog, editable } = this.state;
|
||||
if (showEditDialog) {
|
||||
return (
|
||||
@@ -144,11 +157,13 @@ export class CommentContainer extends Component<InnerProps, State> {
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
<ReplyButton
|
||||
id={`comments-commentContainer-replyButton-${comment.id}`}
|
||||
onClick={this.openReplyDialog}
|
||||
active={showReplyDialog}
|
||||
/>
|
||||
{!disableReplies && (
|
||||
<ReplyButton
|
||||
id={`comments-commentContainer-replyButton-${comment.id}`}
|
||||
onClick={this.openReplyDialog}
|
||||
active={showReplyDialog}
|
||||
/>
|
||||
)}
|
||||
<PermalinkButtonContainer commentID={comment.id} />
|
||||
</>
|
||||
}
|
||||
@@ -158,6 +173,7 @@ export class CommentContainer extends Component<InnerProps, State> {
|
||||
comment={comment}
|
||||
asset={asset}
|
||||
onClose={this.closeReplyDialog}
|
||||
localReply={localReply}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, { Component } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import withFragmentContainer from "talk-framework/lib/relay/withFragmentContainer";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { LocalReplyListContainer_asset as AssetData } from "talk-stream/__generated__/LocalReplyListContainer_asset.graphql";
|
||||
import { LocalReplyListContainer_comment as CommentData } from "talk-stream/__generated__/LocalReplyListContainer_comment.graphql";
|
||||
import { LocalReplyListContainer_me as MeData } from "talk-stream/__generated__/LocalReplyListContainer_me.graphql";
|
||||
|
||||
import ReplyList from "../components/ReplyList";
|
||||
|
||||
interface InnerProps {
|
||||
indentLevel: number;
|
||||
me: MeData;
|
||||
asset: AssetData;
|
||||
comment: CommentData;
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalReplyListContainer renders the replies from the endpoint
|
||||
* `localReplies` instead of `replies`. This is e.g. used for the
|
||||
* ultimate threading level to only display the newly created comments
|
||||
* from the current user.
|
||||
*/
|
||||
export class LocalReplyListContainer extends Component<InnerProps> {
|
||||
public render() {
|
||||
if (!this.props.comment.localReplies) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ReplyList
|
||||
me={this.props.me}
|
||||
comment={this.props.comment}
|
||||
comments={this.props.comment.localReplies}
|
||||
asset={this.props.asset}
|
||||
indentLevel={this.props.indentLevel}
|
||||
disableReplies
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<InnerProps>({
|
||||
me: graphql`
|
||||
fragment LocalReplyListContainer_me on User {
|
||||
...CommentContainer_me
|
||||
}
|
||||
`,
|
||||
asset: graphql`
|
||||
fragment LocalReplyListContainer_asset on Asset {
|
||||
...CommentContainer_asset
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
fragment LocalReplyListContainer_comment on Comment {
|
||||
id
|
||||
localReplies {
|
||||
id
|
||||
...CommentContainer_comment
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(LocalReplyListContainer);
|
||||
|
||||
export type LocalReplyListContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -92,7 +92,7 @@ it("save values", async () => {
|
||||
|
||||
it("creates a comment", async () => {
|
||||
const assetID = "asset-id";
|
||||
const input = { body: "Hello World!" };
|
||||
const input = { body: "Hello World!", local: false };
|
||||
const createCommentStub = sinon.stub();
|
||||
const form = { reset: noop };
|
||||
const onCloseStub = sinon.stub();
|
||||
|
||||
@@ -25,6 +25,7 @@ interface InnerProps {
|
||||
asset: AssetData;
|
||||
onClose?: () => void;
|
||||
autofocus: boolean;
|
||||
localReply?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -76,6 +77,7 @@ export class ReplyCommentFormContainer extends Component<InnerProps, State> {
|
||||
await this.props.createComment({
|
||||
assetID: this.props.asset.id,
|
||||
parentID: this.props.comment.id,
|
||||
local: this.props.localReply,
|
||||
...input,
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ it("renders correctly", () => {
|
||||
me: null,
|
||||
indentLevel: 1,
|
||||
ReplyListComponent: () => null,
|
||||
localReply: false,
|
||||
};
|
||||
const wrapper = shallow(<ReplyListContainerN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -50,6 +51,7 @@ it("renders correctly when replies are empty", () => {
|
||||
me: null,
|
||||
indentLevel: 1,
|
||||
ReplyListComponent: undefined,
|
||||
localReply: false,
|
||||
};
|
||||
const wrapper = shallow(<ReplyListContainerN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -75,6 +77,7 @@ describe("when has more replies", () => {
|
||||
me: null,
|
||||
indentLevel: 1,
|
||||
ReplyListComponent: undefined,
|
||||
localReply: false,
|
||||
};
|
||||
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { graphql, GraphQLTaggedNode, RelayPaginationProp } from "react-relay";
|
||||
import { withProps } from "recompose";
|
||||
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { ReplyListContainer1_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer1_asset.graphql";
|
||||
import { ReplyListContainer1_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer1_comment.graphql";
|
||||
import { ReplyListContainer1_me as MeData } from "talk-stream/__generated__/ReplyListContainer1_me.graphql";
|
||||
@@ -11,7 +12,9 @@ import {
|
||||
ReplyListContainer1PaginationQueryVariables,
|
||||
} from "talk-stream/__generated__/ReplyListContainer1PaginationQuery.graphql";
|
||||
|
||||
import { StatelessComponent } from "enzyme";
|
||||
import ReplyList from "../components/ReplyList";
|
||||
import LocalReplyListContainer from "./LocalReplyListContainer";
|
||||
|
||||
export interface InnerProps {
|
||||
me: MeData | null;
|
||||
@@ -20,6 +23,7 @@ export interface InnerProps {
|
||||
relay: RelayPaginationProp;
|
||||
indentLevel: number;
|
||||
ReplyListComponent: React.ComponentType<any> | undefined;
|
||||
localReply: boolean | undefined;
|
||||
}
|
||||
|
||||
// TODO: (cvle) This should be autogenerated.
|
||||
@@ -52,6 +56,7 @@ export class ReplyListContainer extends React.Component<InnerProps> {
|
||||
disableShowAll={this.state.disableShowAll}
|
||||
indentLevel={this.props.indentLevel}
|
||||
ReplyListComponent={this.props.ReplyListComponent}
|
||||
localReply={this.props.localReply}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -83,9 +88,10 @@ function createReplyListContainer(
|
||||
comment: GraphQLTaggedNode;
|
||||
},
|
||||
query: GraphQLTaggedNode,
|
||||
ReplyListComponent?: React.ComponentType<any>
|
||||
ReplyListComponent?: React.ComponentType<any>,
|
||||
localReply?: boolean
|
||||
) {
|
||||
return withProps({ indentLevel, ReplyListComponent })(
|
||||
return withProps({ indentLevel, ReplyListComponent, localReply })(
|
||||
withPaginationContainer<
|
||||
InnerProps,
|
||||
ReplyListContainer1PaginationQueryVariables,
|
||||
@@ -115,17 +121,26 @@ function createReplyListContainer(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* LastReplyList uses the LocalReplyListContainer.
|
||||
*/
|
||||
const LastReplyList: StatelessComponent<
|
||||
PropTypesOf<typeof LocalReplyListContainer>
|
||||
> = props => <LocalReplyListContainer {...props} indentLevel={6} />;
|
||||
|
||||
const ReplyListContainer5 = createReplyListContainer(
|
||||
5,
|
||||
{
|
||||
me: graphql`
|
||||
fragment ReplyListContainer5_me on User {
|
||||
...CommentContainer_me
|
||||
...LocalReplyListContainer_me
|
||||
}
|
||||
`,
|
||||
asset: graphql`
|
||||
fragment ReplyListContainer5_asset on Asset {
|
||||
...CommentContainer_asset
|
||||
...LocalReplyListContainer_asset
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
@@ -142,6 +157,7 @@ const ReplyListContainer5 = createReplyListContainer(
|
||||
node {
|
||||
id
|
||||
...CommentContainer_comment
|
||||
...LocalReplyListContainer_comment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +178,9 @@ const ReplyListContainer5 = createReplyListContainer(
|
||||
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
|
||||
}
|
||||
}
|
||||
`
|
||||
`,
|
||||
LastReplyList,
|
||||
true
|
||||
);
|
||||
|
||||
const ReplyListContainer4 = createReplyListContainer(
|
||||
|
||||
+26
@@ -1,5 +1,31 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`hide reply button 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>
|
||||
<withContext(withLocalStateContainer(PermalinkContainer))
|
||||
commentID="comment-id"
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
id="comment-comment-id"
|
||||
indentLevel={1}
|
||||
showEditedMarker={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`renders body only 1`] = `
|
||||
<React.Fragment>
|
||||
<IndentedComment
|
||||
|
||||
+4
@@ -39,6 +39,7 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
disableShowAll={false}
|
||||
indentLevel={1}
|
||||
localReply={false}
|
||||
me={null}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
@@ -85,6 +86,7 @@ exports[`when has more replies renders hasMore 1`] = `
|
||||
disableShowAll={false}
|
||||
hasMore={true}
|
||||
indentLevel={1}
|
||||
localReply={false}
|
||||
me={null}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
@@ -129,6 +131,7 @@ exports[`when has more replies when showing all disables show all button 1`] = `
|
||||
disableShowAll={true}
|
||||
hasMore={true}
|
||||
indentLevel={1}
|
||||
localReply={false}
|
||||
me={null}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
@@ -173,6 +176,7 @@ exports[`when has more replies when showing all enable show all button after loa
|
||||
disableShowAll={false}
|
||||
hasMore={true}
|
||||
indentLevel={1}
|
||||
localReply={false}
|
||||
me={null}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user