Implement edit

This commit is contained in:
Chi Vinh Le
2018-09-11 00:15:27 +02:00
parent 195d61d104
commit a36d944e4a
21 changed files with 494 additions and 36 deletions
@@ -6,6 +6,3 @@
.footer:not(:empty) {
margin-top: var(--spacing-unit);
}
.reply {
margin-top: var(--spacing-unit);
}
@@ -5,28 +5,37 @@ import { Flex } from "talk-ui/components";
import * as styles from "./Comment.css";
import HTMLContent from "./HTMLContent";
import Timestamp from "./Timestamp";
import TopBar from "./TopBar";
import TopBarLeft from "./TopBarLeft";
import Username from "./Username";
export interface CommentProps {
id: string;
className?: string;
author: {
username: string | null;
} | null;
body: string | null;
createdAt: string;
topBarRight?: ReactElement<any> | Array<ReactElement<any>>;
footer?: ReactElement<any> | Array<ReactElement<any>>;
}
const Comment: StatelessComponent<CommentProps> = props => {
return (
<div role="article" className={styles.root}>
<TopBar className={styles.topBar}>
{props.author &&
props.author.username && <Username>{props.author.username}</Username>}
<Timestamp>{props.createdAt}</Timestamp>
</TopBar>
<Flex
className={styles.topBar}
direction="row"
justifyContent="space-between"
>
<TopBarLeft>
{props.author &&
props.author.username && (
<Username>{props.author.username}</Username>
)}
<Timestamp>{props.createdAt}</Timestamp>
</TopBarLeft>
<div>{props.topBarRight}</div>
</Flex>
<HTMLContent>{props.body || ""}</HTMLContent>
<Flex className={styles.footer} direction="row" itemGutter="half">
{props.footer}
@@ -4,10 +4,10 @@ import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-framework/types";
import { UIContext, UIContextProps } from "talk-ui/components";
import TopBar from "./TopBar";
import TopBarLeft from "./TopBarLeft";
it("renders correctly on small screens", () => {
const props: PropTypesOf<typeof TopBar> = {
const props: PropTypesOf<typeof TopBarLeft> = {
children: <div>Hello World</div>,
};
@@ -19,14 +19,14 @@ it("renders correctly on small screens", () => {
const testRenderer = TestRenderer.create(
<UIContext.Provider value={context}>
<TopBar {...props} />
<TopBarLeft {...props} />
</UIContext.Provider>
);
expect(testRenderer.toJSON()).toMatchSnapshot();
});
it("renders correctly on big screens", () => {
const props: PropTypesOf<typeof TopBar> = {
const props: PropTypesOf<typeof TopBarLeft> = {
children: <div>Hello World</div>,
};
@@ -38,7 +38,7 @@ it("renders correctly on big screens", () => {
const testRenderer = TestRenderer.create(
<UIContext.Provider value={context}>
<TopBar {...props} />
<TopBarLeft {...props} />
</UIContext.Provider>
);
expect(testRenderer.toJSON()).toMatchSnapshot();
@@ -4,12 +4,12 @@ import { StatelessComponent } from "react";
import { Flex, MatchMedia } from "talk-ui/components";
export interface TopBarProps {
export interface TopBarLeftProps {
className?: string;
children: React.ReactNode;
}
const TopBar: StatelessComponent<TopBarProps> = props => {
const TopBarLeft: StatelessComponent<TopBarLeftProps> = props => {
const rootClassName = cn(props.className);
return (
<MatchMedia gtWidth="xs">
@@ -27,4 +27,4 @@ const TopBar: StatelessComponent<TopBarProps> = props => {
);
};
export default TopBar;
export default TopBarLeft;
@@ -1 +1,4 @@
export { default, default as IndentedComment } from "./IndentedComment";
export { default as TopBarLeft } from "./TopBarLeft";
export { default as Username } from "./Username";
export { default as Timestamp } from "./Timestamp";
@@ -0,0 +1,156 @@
import { CoralRTE } from "@coralproject/rte";
import { Localized } from "fluent-react/compat";
import React, {
EventHandler,
MouseEvent,
Ref,
StatelessComponent,
} from "react";
import { Field, Form } from "react-final-form";
import { OnSubmit } from "talk-framework/lib/form";
import { required } from "talk-framework/lib/validation";
import {
AriaInfo,
Button,
Flex,
HorizontalGutter,
RelativeTime,
Typography,
ValidationMessage,
} from "talk-ui/components";
import { Timestamp, TopBarLeft, Username } from "./Comment";
import RTE from "./RTE";
interface FormProps {
body: string;
}
export interface EditCommentFormProps {
id: string;
className?: string;
author: {
username: string | null;
} | null;
createdAt: string;
editableUntil: string;
onSubmit: OnSubmit<FormProps>;
onCancel?: EventHandler<MouseEvent<any>>;
onClose?: EventHandler<MouseEvent<any>>;
initialValues?: FormProps;
rteRef?: Ref<CoralRTE>;
expired?: boolean;
}
const EditCommentForm: StatelessComponent<EditCommentFormProps> = props => {
const inputID = `comments-editCommentForm-rte-${props.id}`;
return (
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
{({ handleSubmit, submitting, hasValidationErrors, pristine }) => (
<form
className={props.className}
autoComplete="off"
onSubmit={handleSubmit}
id={`comments-editCommentForm-form-${props.id}`}
>
<HorizontalGutter>
<div>
<TopBarLeft>
{props.author &&
props.author.username && (
<Username>{props.author.username}</Username>
)}
<Timestamp>{props.createdAt}</Timestamp>
</TopBarLeft>
</div>
<Field name="body" validate={required}>
{({ input, meta }) => (
<div>
<Localized id="comments-editCommentForm-rteLabel">
<AriaInfo component="label" htmlFor={inputID}>
Edit comment
</AriaInfo>
</Localized>
<Localized
id="comments-editCommentForm-rte"
attrs={{ placeholder: true }}
>
<RTE
inputId={inputID}
onChange={({ html }) => input.onChange(html)}
value={input.value}
placeholder="Edit comment"
forwardRef={props.rteRef}
disabled={submitting || props.expired}
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<Typography align="right" color="error" gutterBottom>
{meta.error || meta.submitError}
</Typography>
)}
</div>
)}
</Field>
{props.expired ? (
<Localized id="comments-editCommentForm-editTimeExpired">
<ValidationMessage fullWidth>
Edit time has expired. You can no longer edit this comment.
Why not post another one?
</ValidationMessage>
</Localized>
) : (
<ValidationMessage fullWidth>
<Localized
id="comments-editCommentForm-editRemainingTime"
time={<RelativeTime date={props.editableUntil} live />}
>
<span>{"Edit: <time></time> remaining"}</span>
</Localized>
</ValidationMessage>
)}
<Flex direction="row" justifyContent="flex-end" itemGutter="half">
{props.expired ? (
<Localized id="comments-editCommentForm-close">
<Button
variant="outlined"
disabled={submitting}
onClick={props.onClose}
>
Close
</Button>
</Localized>
) : (
<>
<Localized id="comments-editCommentForm-cancel">
<Button
variant="outlined"
disabled={submitting}
onClick={props.onCancel}
>
Cancel
</Button>
</Localized>
<Localized id="comments-editCommentForm-saveChanges">
<Button
color="primary"
variant="filled"
disabled={submitting || hasValidationErrors || pristine}
type="submit"
>
Save Changes
</Button>
</Localized>
</>
)}
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
};
export default EditCommentForm;