mirror of
https://github.com/wassname/talk.git
synced 2026-09-12 13:01:11 +08:00
Implement edit
This commit is contained in:
@@ -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}
|
||||
|
||||
+5
-5
@@ -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();
|
||||
+3
-3
@@ -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;
|
||||
Reference in New Issue
Block a user