mirror of
https://github.com/wassname/talk.git
synced 2026-07-10 22:00:56 +08:00
WIP
This commit is contained in:
Generated
+3
-3
@@ -2162,9 +2162,9 @@
|
||||
}
|
||||
},
|
||||
"@types/recompose": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/recompose/-/recompose-0.26.1.tgz",
|
||||
"integrity": "sha512-S5fkitL277yWCEHDzgb/3aJ4RySeqSC7L3Xo+7AlDk6+DAPpQAfF0iwgfjAqbP49JAjVCY+asPQxFPiw1+4CYg==",
|
||||
"version": "0.26.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/recompose/-/recompose-0.26.4.tgz",
|
||||
"integrity": "sha512-QOLPlsBxn/yOxSv4Au66kd8KvYZRCgZA3vV5pNZ6YTEY4GeDHNoYL+sCnbzGIcmWDYoN7PUNZSopaGhvHQperw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@
|
||||
"@types/react-relay": "^1.3.9",
|
||||
"@types/react-responsive": "^3.0.1",
|
||||
"@types/react-test-renderer": "^16.0.1",
|
||||
"@types/recompose": "^0.26.1",
|
||||
"@types/recompose": "^0.26.4",
|
||||
"@types/relay-runtime": "^1.3.6",
|
||||
"@types/sane": "^2.0.0",
|
||||
"@types/sinon": "^5.0.1",
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
hoistStatics,
|
||||
InferableComponentEnhancer,
|
||||
wrapDisplayName,
|
||||
} from "recompose";
|
||||
import React from "react";
|
||||
import { hoistStatics, wrapDisplayName } from "recompose";
|
||||
|
||||
import { Omit } from "talk-ui/types";
|
||||
import { TalkContext, TalkContextConsumer } from "./TalkContext";
|
||||
|
||||
// Injects props and removes them from the prop requirements.
|
||||
// Will not pass through the injected props if they are passed in during
|
||||
// render. Also adds new prop requirements from TNeedsProps.
|
||||
type InferableComponentEnhancerWithProps<TInjectedProps> = <
|
||||
P extends TInjectedProps
|
||||
>(
|
||||
component: React.ComponentType<P>
|
||||
) => React.ComponentType<Omit<P, keyof TInjectedProps>>;
|
||||
|
||||
/**
|
||||
* withContext is a HOC wrapper around `TalkContextConsumer`.
|
||||
* `propsCallback` must be provided which accepts the `TalkContext`
|
||||
@@ -14,7 +20,7 @@ import { TalkContext, TalkContextConsumer } from "./TalkContext";
|
||||
*/
|
||||
function withContext<T>(
|
||||
propsCallback: (context: TalkContext) => T
|
||||
): InferableComponentEnhancer<T> {
|
||||
): InferableComponentEnhancerWithProps<T> {
|
||||
return hoistStatics<T>(
|
||||
<U extends T>(WrappedComponent: React.ComponentType<U>) => {
|
||||
const Component: React.StatelessComponent<any> = props => (
|
||||
@@ -27,7 +33,7 @@ function withContext<T>(
|
||||
Component.displayName = wrapDisplayName(WrappedComponent, "withContext");
|
||||
return Component;
|
||||
}
|
||||
);
|
||||
) as any;
|
||||
}
|
||||
|
||||
export default withContext;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
.root {
|
||||
}
|
||||
.footer {
|
||||
.topBar {
|
||||
margin-bottom: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
.footer:not(:empty) {
|
||||
margin-top: var(--spacing-unit);
|
||||
}
|
||||
.reply {
|
||||
margin-top: var(--spacing-unit);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import * as styles from "./Comment.css";
|
||||
import React, { ReactElement, StatelessComponent } from "react";
|
||||
|
||||
import PermalinkButtonContainer from "../../containers/PermalinkButtonContainer";
|
||||
import { Flex } from "talk-ui/components";
|
||||
|
||||
import * as styles from "./Comment.css";
|
||||
import HTMLContent from "./HTMLContent";
|
||||
import Timestamp from "./Timestamp";
|
||||
import TopBar from "./TopBar";
|
||||
@@ -16,20 +16,21 @@ export interface CommentProps {
|
||||
} | null;
|
||||
body: string | null;
|
||||
createdAt: string;
|
||||
footer?: ReactElement<any> | Array<ReactElement<any>>;
|
||||
}
|
||||
|
||||
const Comment: StatelessComponent<CommentProps> = props => {
|
||||
return (
|
||||
<div role="article" className={styles.root}>
|
||||
<TopBar>
|
||||
<TopBar className={styles.topBar}>
|
||||
{props.author &&
|
||||
props.author.username && <Username>{props.author.username}</Username>}
|
||||
<Timestamp>{props.createdAt}</Timestamp>
|
||||
</TopBar>
|
||||
<HTMLContent>{props.body || ""}</HTMLContent>
|
||||
<div className={styles.footer}>
|
||||
<PermalinkButtonContainer commentID={props.id} />
|
||||
</div>
|
||||
<Flex className={styles.footer} direction="row" itemGutter="half">
|
||||
{props.footer}
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { EventHandler, MouseEvent, StatelessComponent } from "react";
|
||||
|
||||
import { Button, ButtonIcon, MatchMedia } from "talk-ui/components";
|
||||
|
||||
interface Props {
|
||||
onClick?: EventHandler<MouseEvent<HTMLButtonElement>>;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
const ReplyButton: StatelessComponent<Props> = props => (
|
||||
<Button
|
||||
onClick={props.onClick}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
active={props.active}
|
||||
>
|
||||
<MatchMedia gtWidth="xs">
|
||||
<ButtonIcon>reply</ButtonIcon>
|
||||
</MatchMedia>
|
||||
<Localized id="comments-replyButton-reply">
|
||||
<span>Reply</span>
|
||||
</Localized>
|
||||
</Button>
|
||||
);
|
||||
|
||||
export default ReplyButton;
|
||||
@@ -1,3 +0,0 @@
|
||||
.root {
|
||||
margin-bottom: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
@@ -4,15 +4,13 @@ import { StatelessComponent } from "react";
|
||||
|
||||
import { Flex, MatchMedia } from "talk-ui/components";
|
||||
|
||||
import * as styles from "./TopBar.css";
|
||||
|
||||
export interface TopBarProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const TopBar: StatelessComponent<TopBarProps> = props => {
|
||||
const rootClassName = cn(styles.root, props.className);
|
||||
const rootClassName = cn(props.className);
|
||||
return (
|
||||
<MatchMedia gtWidth="xs">
|
||||
{matches => (
|
||||
|
||||
@@ -8,7 +8,8 @@ import CommentContainer from "../containers/CommentContainer";
|
||||
import * as styles from "./PermalinkView.css";
|
||||
|
||||
export interface PermalinkViewProps {
|
||||
comment: PropTypesOf<typeof CommentContainer>["data"] | null;
|
||||
asset: PropTypesOf<typeof CommentContainer>["asset"];
|
||||
comment: PropTypesOf<typeof CommentContainer>["comment"] | null;
|
||||
showAllCommentsHref: string | null;
|
||||
onShowAllComments: (e: MouseEvent<any>) => void;
|
||||
}
|
||||
@@ -16,6 +17,7 @@ export interface PermalinkViewProps {
|
||||
const PermalinkView: StatelessComponent<PermalinkViewProps> = ({
|
||||
showAllCommentsHref,
|
||||
comment,
|
||||
asset,
|
||||
onShowAllComments,
|
||||
}) => {
|
||||
return (
|
||||
@@ -42,7 +44,7 @@ const PermalinkView: StatelessComponent<PermalinkViewProps> = ({
|
||||
<Typography>Comment not found</Typography>
|
||||
</Localized>
|
||||
)}
|
||||
{comment && <CommentContainer data={comment} />}
|
||||
{comment && <CommentContainer comment={comment} asset={asset} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.textarea {
|
||||
composes: bodyCopy from "talk-ui/shared/typography.css";
|
||||
display: block;
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: var(--spacing-unit);
|
||||
border-radius: var(--round-corners);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.poweredBy {
|
||||
margin-top: calc(-0.5 * var(--spacing-unit));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { FormState } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { EventHandler, MouseEvent, StatelessComponent } from "react";
|
||||
import { Field, Form, FormSpy } from "react-final-form";
|
||||
|
||||
import { OnSubmit } from "talk-framework/lib/form";
|
||||
import { required } from "talk-framework/lib/validation";
|
||||
import {
|
||||
AriaInfo,
|
||||
Button,
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import RTE from "./RTE";
|
||||
|
||||
interface FormProps {
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface ReplyCommentFormProps {
|
||||
className?: string;
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onCancel?: EventHandler<MouseEvent<any>>;
|
||||
onChange?: (state: FormState) => void;
|
||||
initialValues?: FormProps;
|
||||
}
|
||||
|
||||
const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => (
|
||||
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
|
||||
{({ handleSubmit, submitting }) => (
|
||||
<form
|
||||
className={props.className}
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit}
|
||||
id="comments-replyCommentForm-form"
|
||||
>
|
||||
<FormSpy onChange={props.onChange} />
|
||||
<HorizontalGutter>
|
||||
<Field name="body" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<div>
|
||||
<Localized id="comments-replyCommentForm-rteLabel">
|
||||
<AriaInfo
|
||||
component="label"
|
||||
htmlFor="comments-replyCommentForm-field"
|
||||
>
|
||||
Write a reply
|
||||
</AriaInfo>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="comments-replyCommentForm-rte"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<RTE
|
||||
inputId="comments-replyCommentForm-field"
|
||||
onChange={({ html }) => input.onChange(html)}
|
||||
value={input.value}
|
||||
placeholder="Write a reply"
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<Typography align="right" color="error" gutterBottom>
|
||||
{meta.error || meta.submitError}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Flex direction="row" justifyContent="flex-end" itemGutter="half">
|
||||
<Localized id="comments-replyCommentForm-cancel">
|
||||
<Button
|
||||
variant="outlined"
|
||||
disabled={submitting}
|
||||
onClick={props.onCancel}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Localized>
|
||||
<Localized id="comments-replyCommentForm-submit">
|
||||
<Button
|
||||
color="primary"
|
||||
variant="filled"
|
||||
disabled={submitting}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
|
||||
export default ReplyCommentForm;
|
||||
@@ -9,9 +9,12 @@ import CommentContainer from "../containers/CommentContainer";
|
||||
import Indent from "./Indent";
|
||||
|
||||
export interface ReplyListProps {
|
||||
commentID: string;
|
||||
asset: PropTypesOf<typeof CommentContainer>["asset"];
|
||||
comment: {
|
||||
id: string;
|
||||
};
|
||||
comments: ReadonlyArray<
|
||||
{ id: string } & PropTypesOf<typeof CommentContainer>["data"]
|
||||
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"]
|
||||
>;
|
||||
onShowAll: () => void;
|
||||
hasMore: boolean;
|
||||
@@ -22,17 +25,21 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
|
||||
return (
|
||||
<Indent>
|
||||
<HorizontalGutter
|
||||
id={`talk-comments-replyList-log--${props.commentID}`}
|
||||
id={`talk-comments-replyList-log--${props.comment.id}`}
|
||||
role="log"
|
||||
>
|
||||
{props.comments.map(comment => (
|
||||
<CommentContainer key={comment.id} data={comment} />
|
||||
<CommentContainer
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
asset={props.asset}
|
||||
/>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-replyList-showAll">
|
||||
<Button
|
||||
id={`talk-comments-replyList-showAll--${props.commentID}`}
|
||||
aria-controls={`talk-comments-replyList-log--${props.commentID}`}
|
||||
id={`talk-comments-replyList-showAll--${props.comment.id}`}
|
||||
aria-controls={`talk-comments-replyList-log--${props.comment.id}`}
|
||||
onClick={props.onShowAll}
|
||||
disabled={props.disableShowAll}
|
||||
variant="outlined"
|
||||
|
||||
@@ -13,10 +13,13 @@ import PostCommentFormFake from "./PostCommentFormFake";
|
||||
import * as styles from "./Stream.css";
|
||||
|
||||
export interface StreamProps {
|
||||
assetID: string;
|
||||
isClosed?: boolean;
|
||||
asset: {
|
||||
id: string;
|
||||
isClosed?: boolean;
|
||||
} & PropTypesOf<typeof CommentContainer>["asset"] &
|
||||
PropTypesOf<typeof ReplyListContainer>["asset"];
|
||||
comments: ReadonlyArray<
|
||||
{ id: string } & PropTypesOf<typeof CommentContainer>["data"] &
|
||||
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"] &
|
||||
PropTypesOf<typeof ReplyListContainer>["comment"]
|
||||
>;
|
||||
onLoadMore?: () => void;
|
||||
@@ -31,7 +34,7 @@ const Stream: StatelessComponent<StreamProps> = props => {
|
||||
<HorizontalGutter size="half">
|
||||
<UserBoxContainer user={props.user} />
|
||||
{props.user ? (
|
||||
<PostCommentFormContainer assetID={props.assetID} />
|
||||
<PostCommentFormContainer assetID={props.asset.id} />
|
||||
) : (
|
||||
<PostCommentFormFake />
|
||||
)}
|
||||
@@ -43,8 +46,8 @@ const Stream: StatelessComponent<StreamProps> = props => {
|
||||
>
|
||||
{props.comments.map(comment => (
|
||||
<HorizontalGutter key={comment.id}>
|
||||
<CommentContainer data={comment} />
|
||||
<ReplyListContainer comment={comment} />
|
||||
<CommentContainer comment={comment} asset={props.asset} />
|
||||
<ReplyListContainer comment={comment} asset={props.asset} />
|
||||
</HorizontalGutter>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
|
||||
@@ -1,37 +1,81 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
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 { CommentContainer as Data } from "talk-stream/__generated__/CommentContainer.graphql";
|
||||
import { CommentContainer_asset as AssetData } from "talk-stream/__generated__/CommentContainer_asset.graphql";
|
||||
import { CommentContainer_comment as CommentData } from "talk-stream/__generated__/CommentContainer_comment.graphql";
|
||||
|
||||
import Comment from "../components/Comment";
|
||||
import ReplyButton from "../components/Comment/ReplyButton";
|
||||
import ReplyCommentFormContainer from ".//ReplyCommentFormContainer";
|
||||
import PermalinkButtonContainer from "./PermalinkButtonContainer";
|
||||
|
||||
interface InnerProps {
|
||||
data: Data;
|
||||
comment: CommentData;
|
||||
asset: AssetData;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
graphql`
|
||||
fragment CommentContainer_comment on Comment {
|
||||
id
|
||||
author {
|
||||
username
|
||||
}
|
||||
body
|
||||
createdAt
|
||||
}
|
||||
`;
|
||||
interface State {
|
||||
showReplyDialog: boolean;
|
||||
}
|
||||
|
||||
export const CommentContainer: StatelessComponent<InnerProps> = props => {
|
||||
const { data, ...rest } = props;
|
||||
return <Comment {...rest} {...props.data} />;
|
||||
};
|
||||
export class CommentContainer extends Component<InnerProps, State> {
|
||||
public state = {
|
||||
showReplyDialog: false,
|
||||
};
|
||||
|
||||
private toggleReplyDialog = () => {
|
||||
this.setState(state => ({
|
||||
showReplyDialog: !state.showReplyDialog,
|
||||
}));
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { comment, asset, ...rest } = this.props;
|
||||
const { showReplyDialog } = this.state;
|
||||
return (
|
||||
<>
|
||||
<Comment
|
||||
{...rest}
|
||||
{...comment}
|
||||
footer={
|
||||
<>
|
||||
<ReplyButton
|
||||
onClick={this.toggleReplyDialog}
|
||||
active={showReplyDialog}
|
||||
/>
|
||||
<PermalinkButtonContainer commentID={comment.id} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{showReplyDialog && (
|
||||
<ReplyCommentFormContainer
|
||||
comment={comment}
|
||||
asset={asset}
|
||||
onCancel={this.toggleReplyDialog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<InnerProps>({
|
||||
data: graphql`
|
||||
fragment CommentContainer on Comment {
|
||||
...CommentContainer_comment @relay(mask: false)
|
||||
asset: graphql`
|
||||
fragment CommentContainer_asset on Asset {
|
||||
...ReplyCommentFormContainer_asset
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
fragment CommentContainer_comment on Comment {
|
||||
id
|
||||
author {
|
||||
username
|
||||
}
|
||||
body
|
||||
createdAt
|
||||
...ReplyCommentFormContainer_comment
|
||||
}
|
||||
`,
|
||||
})(CommentContainer);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { graphql } from "react-relay";
|
||||
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 {
|
||||
SetCommentIDMutation,
|
||||
@@ -15,6 +16,7 @@ import PermalinkView from "../components/PermalinkView";
|
||||
|
||||
interface PermalinkViewContainerProps {
|
||||
comment: CommentData | null;
|
||||
asset: AssetData;
|
||||
setCommentID: SetCommentIDMutation;
|
||||
pym: PymChild | undefined;
|
||||
}
|
||||
@@ -37,9 +39,10 @@ class PermalinkViewContainer extends React.Component<
|
||||
return buildURL({ ...urlParts, search });
|
||||
}
|
||||
public render() {
|
||||
const { comment } = this.props;
|
||||
const { comment, asset } = this.props;
|
||||
return (
|
||||
<PermalinkView
|
||||
asset={asset}
|
||||
comment={comment}
|
||||
showAllCommentsHref={this.getShowAllCommentsHref()}
|
||||
onShowAllComments={this.showAllComments}
|
||||
@@ -53,9 +56,14 @@ const enhanced = withContext(ctx => ({
|
||||
}))(
|
||||
withSetCommentIDMutation(
|
||||
withFragmentContainer<PermalinkViewContainerProps>({
|
||||
asset: graphql`
|
||||
fragment PermalinkViewContainer_asset on Asset {
|
||||
...CommentContainer_asset
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
fragment PermalinkViewContainer_comment on Comment {
|
||||
...CommentContainer
|
||||
...CommentContainer_comment
|
||||
}
|
||||
`,
|
||||
})(PermalinkViewContainer)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import React, { Component, EventHandler, MouseEvent } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withContext } from "talk-framework/lib/bootstrap";
|
||||
import { BadUserInputError } from "talk-framework/lib/errors";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { PymStorage } from "talk-framework/lib/storage";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { ReplyCommentFormContainer_asset as AssetData } from "talk-stream/__generated__/ReplyCommentFormContainer_asset.graphql";
|
||||
import { ReplyCommentFormContainer_comment as CommentData } from "talk-stream/__generated__/ReplyCommentFormContainer_comment.graphql";
|
||||
|
||||
import ReplyCommentForm, {
|
||||
ReplyCommentFormProps,
|
||||
} from "../components/ReplyCommentForm";
|
||||
import { CreateCommentMutation, withCreateCommentMutation } from "../mutations";
|
||||
|
||||
interface InnerProps {
|
||||
createComment: CreateCommentMutation;
|
||||
pymSessionStorage: PymStorage;
|
||||
comment: CommentData;
|
||||
asset: AssetData;
|
||||
onCancel?: EventHandler<MouseEvent<any>>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
initialValues?: ReplyCommentFormProps["initialValues"];
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
const contextKey = "postCommentFormBody";
|
||||
|
||||
export class ReplyCommentFormContainer extends Component<InnerProps, State> {
|
||||
public state: State = { initialized: false };
|
||||
|
||||
constructor(props: InnerProps) {
|
||||
super(props);
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init() {
|
||||
const body = await this.props.pymSessionStorage.getItem(contextKey);
|
||||
if (body) {
|
||||
this.setState({
|
||||
initialValues: {
|
||||
body,
|
||||
},
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
initialized: true,
|
||||
});
|
||||
}
|
||||
|
||||
private handleOnSubmit: ReplyCommentFormProps["onSubmit"] = async (
|
||||
input,
|
||||
form
|
||||
) => {
|
||||
try {
|
||||
await this.props.createComment({
|
||||
assetID: this.props.asset.id,
|
||||
...input,
|
||||
});
|
||||
form.reset({});
|
||||
} catch (error) {
|
||||
if (error instanceof BadUserInputError) {
|
||||
return error.invalidArgsLocalized;
|
||||
}
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private handleOnChange: ReplyCommentFormProps["onChange"] = state => {
|
||||
if (state.values.body) {
|
||||
this.props.pymSessionStorage.setItem(contextKey, state.values.body);
|
||||
} else {
|
||||
this.props.pymSessionStorage.removeItem(contextKey);
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (!this.state.initialized) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ReplyCommentForm
|
||||
onSubmit={this.handleOnSubmit}
|
||||
onChange={this.handleOnChange}
|
||||
initialValues={this.state.initialValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
const enhanced = withContext(({ pymSessionStorage }) => ({
|
||||
pymSessionStorage,
|
||||
}))(
|
||||
withCreateCommentMutation(
|
||||
withFragmentContainer<InnerProps>({
|
||||
asset: graphql`
|
||||
fragment ReplyCommentFormContainer_asset on Asset {
|
||||
id
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
fragment ReplyCommentFormContainer_comment on Comment {
|
||||
id
|
||||
}
|
||||
`,
|
||||
})(ReplyCommentFormContainer)
|
||||
)
|
||||
);
|
||||
export type PostCommentFormContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -3,7 +3,8 @@ import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { ReplyListContainer_comment as Data } from "talk-stream/__generated__/ReplyListContainer_comment.graphql";
|
||||
import { ReplyListContainer_asset as AssetData } from "talk-stream/__generated__/ReplyListContainer_asset.graphql";
|
||||
import { ReplyListContainer_comment as CommentData } from "talk-stream/__generated__/ReplyListContainer_comment.graphql";
|
||||
import {
|
||||
COMMENT_SORT,
|
||||
ReplyListContainerPaginationQueryVariables,
|
||||
@@ -12,7 +13,8 @@ import {
|
||||
import ReplyList from "../components/ReplyList";
|
||||
|
||||
export interface InnerProps {
|
||||
comment: Data;
|
||||
asset: AssetData;
|
||||
comment: CommentData;
|
||||
relay: RelayPaginationProp;
|
||||
}
|
||||
|
||||
@@ -31,8 +33,9 @@ export class ReplyListContainer extends React.Component<InnerProps> {
|
||||
const comments = this.props.comment.replies.edges.map(edge => edge.node);
|
||||
return (
|
||||
<ReplyList
|
||||
commentID={this.props.comment.id}
|
||||
comment={this.props.comment}
|
||||
comments={comments}
|
||||
asset={this.props.asset}
|
||||
onShowAll={this.showAll}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableShowAll={this.state.disableShowAll}
|
||||
@@ -72,6 +75,11 @@ const enhanced = withPaginationContainer<
|
||||
FragmentVariables
|
||||
>(
|
||||
{
|
||||
asset: graphql`
|
||||
fragment ReplyListContainer_asset on Asset {
|
||||
...CommentContainer_asset
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
fragment ReplyListContainer_comment on Comment
|
||||
@argumentDefinitions(
|
||||
@@ -85,7 +93,7 @@ const enhanced = withPaginationContainer<
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
...CommentContainer_comment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ export class StreamContainer extends React.Component<InnerProps> {
|
||||
const comments = this.props.asset.comments.edges.map(edge => edge.node);
|
||||
return (
|
||||
<Stream
|
||||
assetID={this.props.asset.id}
|
||||
isClosed={this.props.asset.isClosed}
|
||||
asset={this.props.asset}
|
||||
comments={comments}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
@@ -83,11 +82,13 @@ const enhanced = withPaginationContainer<
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
...CommentContainer_comment
|
||||
...ReplyListContainer_comment
|
||||
}
|
||||
}
|
||||
}
|
||||
...CommentContainer_asset
|
||||
...ReplyListContainer_asset
|
||||
}
|
||||
`,
|
||||
user: graphql`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { ReadyState } from "react-relay";
|
||||
@@ -25,23 +26,36 @@ export const render = ({
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
if (props) {
|
||||
return <PermalinkViewContainer comment={props.comment} />;
|
||||
if (!props.asset) {
|
||||
return (
|
||||
<Localized id="comments-permalinkViewQuery-assetNotFound">
|
||||
<div>Asset not found</div>
|
||||
</Localized>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PermalinkViewContainer comment={props.comment} asset={props.asset} />
|
||||
);
|
||||
}
|
||||
return <Spinner />;
|
||||
};
|
||||
|
||||
const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
|
||||
local: { commentID },
|
||||
local: { commentID, assetID },
|
||||
}) => (
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query PermalinkViewQuery($commentID: ID!) {
|
||||
query PermalinkViewQuery($commentID: ID!, $assetID: ID!) {
|
||||
asset(id: $assetID) {
|
||||
...PermalinkViewContainer_asset
|
||||
}
|
||||
comment(id: $commentID) {
|
||||
...PermalinkViewContainer_comment
|
||||
}
|
||||
}
|
||||
`}
|
||||
variables={{
|
||||
assetID: assetID!,
|
||||
commentID: commentID!,
|
||||
}}
|
||||
render={render}
|
||||
@@ -51,6 +65,8 @@ const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
|
||||
const enhanced = withLocalStateContainer(
|
||||
graphql`
|
||||
fragment PermalinkViewQueryLocal on Local {
|
||||
assetID
|
||||
authRevision
|
||||
commentID
|
||||
}
|
||||
`
|
||||
|
||||
@@ -43,15 +43,15 @@ const StreamQuery: StatelessComponent<InnerProps> = ({
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query StreamQuery($assetID: ID!, $authRevision: Int!) {
|
||||
asset(id: $assetID) {
|
||||
...StreamContainer_asset
|
||||
}
|
||||
# authRevision is increment every time auth state has changed.
|
||||
# This is basically a cache invalidation and causes relay
|
||||
# to automatically update this query.
|
||||
me(clientAuthRevision: $authRevision) {
|
||||
...StreamContainer_user
|
||||
}
|
||||
asset(id: $assetID) {
|
||||
...StreamContainer_asset
|
||||
}
|
||||
}
|
||||
`}
|
||||
variables={{
|
||||
|
||||
@@ -87,7 +87,7 @@ const enhanced = withFragmentContainer<{ data: Data }>({
|
||||
fragment PermalinkViewContainerQuery on Query
|
||||
@argumentDefinitions(commentID: { type: "ID!" }) {
|
||||
comment(id: $commentID) {
|
||||
...CommentContainer
|
||||
...CommentContainer_comment
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
Reference in New Issue
Block a user