aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-02-13 21:23:30 +0800
committercrupest <crupest@outlook.com>2021-02-13 21:23:30 +0800
commitfe5128137f530daf8ca315cb89811121c6c2c9da (patch)
tree34cbdb7895e18d34779b2209a1e9aae0fde20cf2 /FrontEnd/src/app
parentaeed3cb80d8c0a62d0ccc565733c4a213759c5bd (diff)
downloadtimeline-fe5128137f530daf8ca315cb89811121c6c2c9da.tar.gz
timeline-fe5128137f530daf8ca315cb89811121c6c2c9da.tar.bz2
timeline-fe5128137f530daf8ca315cb89811121c6c2c9da.zip
...
Diffstat (limited to 'FrontEnd/src/app')
-rw-r--r--FrontEnd/src/app/App.tsx6
-rw-r--r--FrontEnd/src/app/http/timeline.ts33
-rw-r--r--FrontEnd/src/app/http/user.ts6
-rw-r--r--FrontEnd/src/app/utilities/url.ts3
-rw-r--r--FrontEnd/src/app/views/common/AppBar.tsx10
-rw-r--r--FrontEnd/src/app/views/common/user/UserAvatar.tsx13
-rw-r--r--FrontEnd/src/app/views/home/TimelineBoard.tsx12
-rw-r--r--FrontEnd/src/app/views/timeline-common/Timeline.tsx10
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx10
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx114
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx129
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx4
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx26
13 files changed, 274 insertions, 102 deletions
diff --git a/FrontEnd/src/app/App.tsx b/FrontEnd/src/app/App.tsx
index 0a7513e4..fb57bd1e 100644
--- a/FrontEnd/src/app/App.tsx
+++ b/FrontEnd/src/app/App.tsx
@@ -12,7 +12,6 @@ import TimelinePage from "./views/timeline";
import Search from "./views/search";
import AlertHost from "./views/common/alert/AlertHost";
-import { dataStorage } from "./services/common";
import { userService, useRawUser } from "./services/user";
const NoMatch: React.FC = () => {
@@ -24,16 +23,13 @@ const LazyAdmin = React.lazy(
);
const App: React.FC = () => {
- const [loading, setLoading] = React.useState<boolean>(true);
-
const user = useRawUser();
React.useEffect(() => {
void userService.checkLoginState();
- void dataStorage.ready().then(() => setLoading(false));
}, []);
- if (user === undefined || loading) {
+ if (user === undefined) {
return <LoadingPage />;
} else {
return (
diff --git a/FrontEnd/src/app/http/timeline.ts b/FrontEnd/src/app/http/timeline.ts
index 375a2325..50af259e 100644
--- a/FrontEnd/src/app/http/timeline.ts
+++ b/FrontEnd/src/app/http/timeline.ts
@@ -7,6 +7,7 @@ import {
apiBaseUrl,
extractResponseData,
convertToIfErrorCodeIs,
+ getHttpToken,
} from "./common";
import { HttpUser } from "./user";
@@ -50,20 +51,22 @@ export interface HttpTimelinePostInfo {
id: number;
time: string;
author: HttpUser;
- dataList: HttpTimelinePostDataDigest;
+ dataList: HttpTimelinePostDataDigest[];
color: string;
lastUpdated: string;
timelineName: string;
editable: boolean;
}
+export interface HttpTimelinePostPostRequestData {
+ contentType: string;
+ data: string;
+}
+
export interface HttpTimelinePostPostRequest {
time?: string;
color?: string;
- dataList: {
- contentType: string;
- data: string;
- }[];
+ dataList: HttpTimelinePostPostRequestData[];
}
export interface HttpTimelinePatchRequest {
@@ -91,6 +94,8 @@ export interface IHttpTimelineClient {
memberPut(timelineName: string, username: string): Promise<void>;
memberDelete(timelineName: string, username: string): Promise<void>;
listPost(timelineName: string): Promise<HttpTimelinePostInfo[]>;
+ generatePostDataUrl(timelineName: string, postId: number): string;
+ getPostDataAsString(timelineName: string, postId: number): Promise<string>;
postPost(
timelineName: string,
req: HttpTimelinePostPostRequest
@@ -153,6 +158,24 @@ export class HttpTimelineClient implements IHttpTimelineClient {
.then(extractResponseData);
}
+ generatePostDataUrl(timelineName: string, postId: number): string {
+ return applyQueryParameters(
+ `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`,
+ { token: getHttpToken() }
+ );
+ }
+
+ getPostDataAsString(timelineName: string, postId: number): Promise<string> {
+ return axios
+ .get<string>(
+ `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`,
+ {
+ responseType: "text",
+ }
+ )
+ .then(extractResponseData);
+ }
+
postPost(
timelineName: string,
req: HttpTimelinePostPostRequest
diff --git a/FrontEnd/src/app/http/user.ts b/FrontEnd/src/app/http/user.ts
index c6a567d3..dcb222bf 100644
--- a/FrontEnd/src/app/http/user.ts
+++ b/FrontEnd/src/app/http/user.ts
@@ -61,7 +61,7 @@ export interface IHttpUserClient {
get(username: string): Promise<HttpUser>;
patch(username: string, req: HttpUserPatchRequest): Promise<HttpUser>;
delete(username: string): Promise<void>;
- // return etag
+ generateAvatarUrl(username: string): string;
putAvatar(username: string, data: Blob): Promise<string>;
changePassword(req: HttpChangePasswordRequest): Promise<void>;
putUserPermission(
@@ -100,6 +100,10 @@ export class HttpUserClient implements IHttpUserClient {
return axios.delete(`${apiBaseUrl}/users/${username}`).then();
}
+ generateAvatarUrl(username: string): string {
+ return `${apiBaseUrl}/users/${username}/avatar`;
+ }
+
putAvatar(username: string, data: Blob): Promise<string> {
return axios
.put(`${apiBaseUrl}/users/${username}/avatar`, data, {
diff --git a/FrontEnd/src/app/utilities/url.ts b/FrontEnd/src/app/utilities/url.ts
index 21ad6304..4f2a6ecd 100644
--- a/FrontEnd/src/app/utilities/url.ts
+++ b/FrontEnd/src/app/utilities/url.ts
@@ -4,7 +4,8 @@ export function applyQueryParameters<T>(url: string, query: T): string {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
- if (typeof value === "string") params.set(key, value);
+ if (value == null) void 0;
+ else if (typeof value === "string") params.set(key, value);
else if (typeof value === "number") params.set(key, String(value));
else if (typeof value === "boolean") params.set(key, String(value));
else if (value instanceof Date) params.set(key, value.toISOString());
diff --git a/FrontEnd/src/app/views/common/AppBar.tsx b/FrontEnd/src/app/views/common/AppBar.tsx
index d0e39f98..e682a308 100644
--- a/FrontEnd/src/app/views/common/AppBar.tsx
+++ b/FrontEnd/src/app/views/common/AppBar.tsx
@@ -4,14 +4,13 @@ import { LinkContainer } from "react-router-bootstrap";
import { Navbar, Nav } from "react-bootstrap";
import { NavLink } from "react-router-dom";
-import { useUser, useAvatar } from "@/services/user";
+import { useUser } from "@/services/user";
import TimelineLogo from "./TimelineLogo";
-import BlobImage from "./BlobImage";
+import UserAvatar from "./user/UserAvatar";
const AppBar: React.FC = (_) => {
const user = useUser();
- const avatar = useAvatar(user?.username);
const { t } = useTranslation();
@@ -70,10 +69,9 @@ const AppBar: React.FC = (_) => {
<Nav className="ml-auto mr-2 align-items-center">
{user != null ? (
<LinkContainer to={`/users/${user.username}`}>
- <BlobImage
+ <UserAvatar
+ username={user.username}
className="avatar small rounded-circle bg-white cursor-pointer ml-auto"
- onClick={collapse}
- blob={avatar}
/>
</LinkContainer>
) : (
diff --git a/FrontEnd/src/app/views/common/user/UserAvatar.tsx b/FrontEnd/src/app/views/common/user/UserAvatar.tsx
index 73273298..9e822528 100644
--- a/FrontEnd/src/app/views/common/user/UserAvatar.tsx
+++ b/FrontEnd/src/app/views/common/user/UserAvatar.tsx
@@ -1,8 +1,6 @@
import React from "react";
-import { useAvatar } from "@/services/user";
-
-import BlobImage from "../BlobImage";
+import { getHttpUserClient } from "@/http/user";
export interface UserAvatarProps
extends React.ImgHTMLAttributes<HTMLImageElement> {
@@ -10,9 +8,12 @@ export interface UserAvatarProps
}
const UserAvatar: React.FC<UserAvatarProps> = ({ username, ...otherProps }) => {
- const avatar = useAvatar(username);
-
- return <BlobImage blob={avatar} {...otherProps} />;
+ return (
+ <img
+ src={getHttpUserClient().generateAvatarUrl(username)}
+ {...otherProps}
+ />
+ );
};
export default UserAvatar;
diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx
index c3f01aed..58988b17 100644
--- a/FrontEnd/src/app/views/home/TimelineBoard.tsx
+++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx
@@ -4,10 +4,10 @@ import { Link } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import { Spinner } from "react-bootstrap";
-import { TimelineInfo } from "@/services/timeline";
+import { HttpTimelineInfo } from "@/http/timeline";
+
import TimelineLogo from "../common/TimelineLogo";
import UserTimelineLogo from "../common/UserTimelineLogo";
-import { HttpTimelineInfo } from "@/http/timeline";
interface TimelineBoardItemProps {
timeline: HttpTimelineInfo;
@@ -98,7 +98,7 @@ const TimelineBoardItem: React.FC<TimelineBoardItemProps> = ({
};
interface TimelineBoardItemContainerProps {
- timelines: TimelineInfo[];
+ timelines: HttpTimelineInfo[];
editHandler?: {
// offset may exceed index range plusing index.
onMove: (timeline: string, index: number, offset: number) => void;
@@ -206,7 +206,7 @@ const TimelineBoardItemContainer: React.FC<TimelineBoardItemContainerProps> = ({
interface TimelineBoardUIProps {
title?: string;
- timelines: TimelineInfo[] | "offline" | "loading";
+ timelines: HttpTimelineInfo[] | "offline" | "loading";
onReload: () => void;
className?: string;
editHandler?: {
@@ -304,7 +304,7 @@ const TimelineBoardUI: React.FC<TimelineBoardUIProps> = (props) => {
export interface TimelineBoardProps {
title?: string;
className?: string;
- load: () => Promise<TimelineInfo[]>;
+ load: () => Promise<HttpTimelineInfo[]>;
editHandler?: {
onMove: (timeline: string, index: number, offset: number) => Promise<void>;
onDelete: (timeline: string) => Promise<void>;
@@ -318,7 +318,7 @@ const TimelineBoard: React.FC<TimelineBoardProps> = ({
editHandler,
}) => {
const [timelines, setTimelines] = React.useState<
- TimelineInfo[] | "offline" | "loading"
+ HttpTimelineInfo[] | "offline" | "loading"
>("loading");
React.useEffect(() => {
diff --git a/FrontEnd/src/app/views/timeline-common/Timeline.tsx b/FrontEnd/src/app/views/timeline-common/Timeline.tsx
index d970af84..d41588bb 100644
--- a/FrontEnd/src/app/views/timeline-common/Timeline.tsx
+++ b/FrontEnd/src/app/views/timeline-common/Timeline.tsx
@@ -13,10 +13,12 @@ export interface TimelineProps {
className?: string;
style?: React.CSSProperties;
timelineName: string;
+ reloadKey: number;
+ onReload: () => void;
}
const Timeline: React.FC<TimelineProps> = (props) => {
- const { timelineName, className, style } = props;
+ const { timelineName, className, style, reloadKey, onReload } = props;
const [posts, setPosts] = React.useState<
| HttpTimelinePostInfo[]
@@ -30,6 +32,8 @@ const Timeline: React.FC<TimelineProps> = (props) => {
React.useEffect(() => {
let subscribe = true;
+ setPosts("loading");
+
void getHttpTimelineClient()
.listPost(timelineName)
.then(
@@ -53,7 +57,7 @@ const Timeline: React.FC<TimelineProps> = (props) => {
return () => {
subscribe = false;
};
- }, [timelineName]);
+ }, [timelineName, reloadKey]);
switch (posts) {
case "loading":
@@ -87,7 +91,7 @@ const Timeline: React.FC<TimelineProps> = (props) => {
</div>
);
default:
- return <TimelinePostListView posts={posts} />;
+ return <TimelinePostListView posts={posts} onReload={onReload} />;
}
};
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
index 56be8cfe..d133bd34 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
@@ -49,6 +49,9 @@ export default function TimelinePageTemplateUI<TManageItems>(
const [bottomSpaceHeight, setBottomSpaceHeight] = React.useState<number>(0);
+ const [timelineReloadKey, setTimelineReloadKey] = React.useState<number>(0);
+ const reloadTimeline = (): void => setTimelineReloadKey((old) => old + 1);
+
const onPostEditHeightChange = React.useCallback((height: number): void => {
setBottomSpaceHeight(height);
if (height === 0) {
@@ -122,7 +125,11 @@ export default function TimelinePageTemplateUI<TManageItems>(
minHeight: `calc(100vh - ${56 + bottomSpaceHeight}px)`,
}}
>
- <Timeline timelineName={timeline.name} />
+ <Timeline
+ timelineName={timeline.name}
+ reloadKey={timelineReloadKey}
+ onReload={reloadTimeline}
+ />
</div>
{timeline.postable ? (
<>
@@ -134,6 +141,7 @@ export default function TimelinePageTemplateUI<TManageItems>(
className="fixed-bottom"
timeline={timeline}
onHeightChange={onPostEditHeightChange}
+ onPosted={reloadTimeline}
/>
</>
) : null}
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx
new file mode 100644
index 00000000..69954040
--- /dev/null
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx
@@ -0,0 +1,114 @@
+import React from "react";
+import { Spinner } from "react-bootstrap";
+
+import { HttpNetworkError } from "@/http/common";
+import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
+
+import { useUser } from "@/services/user";
+
+const TextView: React.FC<TimelinePostContentViewProps> = (props) => {
+ const { post, className, style } = props;
+
+ const [text, setText] = React.useState<string | null>(null);
+ const [error, setError] = React.useState<"offline" | "error" | null>(null);
+
+ React.useEffect(() => {
+ let subscribe = true;
+
+ setText(null);
+ setError(null);
+
+ void getHttpTimelineClient()
+ .getPostDataAsString(post.timelineName, post.id)
+ .then(
+ (data) => {
+ if (subscribe) setText(data);
+ },
+ (error) => {
+ if (subscribe) {
+ if (error instanceof HttpNetworkError) {
+ setError("offline");
+ } else {
+ setError("error");
+ }
+ }
+ }
+ );
+
+ return () => {
+ subscribe = false;
+ };
+ }, [post]);
+
+ if (error != null) {
+ // TODO: i18n
+ return (
+ <div className={className} style={style}>
+ Error!
+ </div>
+ );
+ } else if (text == null) {
+ return <Spinner variant="primary" animation="grow" />;
+ } else {
+ return (
+ <div className={className} style={style}>
+ {text}
+ </div>
+ );
+ }
+};
+
+const ImageView: React.FC<TimelinePostContentViewProps> = (props) => {
+ const { post, className, style } = props;
+
+ useUser();
+
+ return (
+ <img
+ src={getHttpTimelineClient().generatePostDataUrl(
+ post.timelineName,
+ post.id
+ )}
+ className={className}
+ style={style}
+ />
+ );
+};
+
+const MarkdownView: React.FC<TimelinePostContentViewProps> = (_props) => {
+ // TODO: Implement this.
+ return <div>Unsupported now!</div>;
+};
+
+export interface TimelinePostContentViewProps {
+ post: HttpTimelinePostInfo;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const viewMap: Record<string, React.FC<TimelinePostContentViewProps>> = {
+ "text/plain": TextView,
+ "text/markdown": MarkdownView,
+ "image/png": ImageView,
+ "image/jpeg": ImageView,
+ "image/gif": ImageView,
+ "image/webp": ImageView,
+};
+
+const TimelinePostContentView: React.FC<TimelinePostContentViewProps> = (
+ props
+) => {
+ const { post, className, style } = props;
+
+ const type = post.dataList[0].kind;
+
+ if (type in viewMap) {
+ const View = viewMap[type];
+ return <View post={post} className={className} style={style} />;
+ } else {
+ // TODO: i18n
+ return <div>Error, unknown post type!</div>;
+ }
+};
+
+export default TimelinePostContentView;
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
index 488b627c..7c49e5bb 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
@@ -5,8 +5,14 @@ import { Button, Spinner, Row, Col, Form } from "react-bootstrap";
import { UiLogicError } from "@/common";
+import {
+ getHttpTimelineClient,
+ HttpTimelineInfo,
+ HttpTimelinePostPostRequestData,
+} from "@/http/timeline";
+
import { pushAlert } from "@/services/alert";
-import { HttpTimelineInfo } from "@/http/timeline";
+import { base64 } from "@/http/common";
interface TimelinePostEditImageProps {
onSelect: (blob: Blob | null) => void;
@@ -77,19 +83,21 @@ const TimelinePostEditImage: React.FC<TimelinePostEditImageProps> = (props) => {
export interface TimelinePostEditProps {
className?: string;
timeline: HttpTimelineInfo;
+ onPosted: () => void;
onHeightChange?: (height: number) => void;
}
const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
- const { t } = useTranslation();
+ const { timeline, onHeightChange, className, onPosted } = props;
- const { timeline } = props;
+ const { t } = useTranslation();
const [state, setState] = React.useState<"input" | "process">("input");
const [kind, setKind] = React.useState<"text" | "image">("text");
const [text, setText] = React.useState<string>("");
+ const [imageBlob, setImageBlob] = React.useState<Blob | null>(null);
- const draftLocalStorageKey = `timeline.${props.timelineUniqueId}.postDraft`;
+ const draftLocalStorageKey = `timeline.${timeline.name}.postDraft`;
React.useEffect(() => {
setText(window.localStorage.getItem(draftLocalStorageKey) ?? "");
@@ -101,77 +109,76 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
const containerRef = React.useRef<HTMLDivElement>(null!);
const notifyHeightChange = (): void => {
- if (props.onHeightChange) {
- props.onHeightChange(containerRef.current.clientHeight);
+ if (onHeightChange) {
+ onHeightChange(containerRef.current.clientHeight);
}
};
React.useEffect(() => {
- if (props.onHeightChange) {
- props.onHeightChange(containerRef.current.clientHeight);
+ if (onHeightChange) {
+ onHeightChange(containerRef.current.clientHeight);
}
return () => {
- if (props.onHeightChange) {
- props.onHeightChange(0);
+ if (onHeightChange) {
+ onHeightChange(0);
}
};
});
const toggleKind = React.useCallback(() => {
- // TODO: Implement this.
- // setKind((oldKind) => (oldKind === "text" ? "image" : "text"));
- // setImageBlob(null);
+ setKind((oldKind) => (oldKind === "text" ? "image" : "text"));
+ setImageBlob(null);
}, []);
- const onSend = React.useCallback(() => {
+ const onSend = async (): Promise<void> => {
setState("process");
- // TODO: Implement this.
-
- // const req: TimelineCreatePostRequest = (() => {
- // switch (kind) {
- // case "text":
- // return {
- // content: {
- // type: "text",
- // text: text,
- // },
- // } as TimelineCreatePostRequest;
- // case "image":
- // if (imageBlob == null) {
- // throw new UiLogicError(
- // "Content type is image but image blob is null."
- // );
- // }
- // return {
- // content: {
- // type: "image",
- // data: imageBlob,
- // },
- // } as TimelineCreatePostRequest;
- // default:
- // throw new UiLogicError("Unknown content type.");
- // }
- // })();
-
- // onPost(req).then(
- // (_) => {
- // if (kind === "text") {
- // setText("");
- // window.localStorage.removeItem(draftLocalStorageKey);
- // }
- // setState("input");
- // setKind("text");
- // },
- // (_) => {
- // pushAlert({
- // type: "danger",
- // message: t("timeline.sendPostFailed"),
- // });
- // setState("input");
- // }
- // );
- }, []);
+ let requestData: HttpTimelinePostPostRequestData;
+ switch (kind) {
+ case "text":
+ requestData = {
+ contentType: "text/plain",
+ data: await base64(new Blob([text])),
+ };
+ break;
+ case "image":
+ if (imageBlob == null) {
+ throw new UiLogicError(
+ "Content type is image but image blob is null."
+ );
+ }
+ requestData = {
+ contentType: imageBlob.type,
+ data: await base64(imageBlob),
+ };
+ break;
+ default:
+ throw new UiLogicError("Unknown content type.");
+ }
+
+ getHttpTimelineClient()
+ .postPost(timeline.name, {
+ dataList: [requestData],
+ })
+ .then(
+ (_) => {
+ if (kind === "text") {
+ setText("");
+ window.localStorage.removeItem(draftLocalStorageKey);
+ }
+ setState("input");
+ setKind("text");
+ onPosted();
+ },
+ (_) => {
+ pushAlert({
+ type: "danger",
+ message: t("timeline.sendPostFailed"),
+ });
+ setState("input");
+ }
+ );
+ };
const onImageSelect = React.useCallback((blob: Blob | null) => {
setImageBlob(blob);
@@ -180,7 +187,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
return (
<div
ref={containerRef}
- className={clsx("container-fluid bg-light", props.className)}
+ className={clsx("container-fluid bg-light", className)}
>
<Row>
<Col className="px-1 py-1">
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx
index 5acc1c21..63255619 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx
@@ -18,10 +18,11 @@ export interface TimelinePostListViewProps {
className?: string;
style?: React.CSSProperties;
posts: HttpTimelinePostInfo[];
+ onReload: () => void;
}
const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => {
- const { className, style, posts } = props;
+ const { className, style, posts, onReload } = props;
const groupedPosts = React.useMemo<
{ date: Date; posts: (HttpTimelinePostInfo & { index: number })[] }[]
@@ -60,6 +61,7 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => {
key={post.id}
post={post}
current={posts.length - 1 === post.index}
+ onDeleted={onReload}
/>
);
})}
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
index a2ae72cf..7fd98310 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
@@ -2,10 +2,13 @@ import React from "react";
import clsx from "clsx";
import { Link } from "react-router-dom";
-import { HttpTimelinePostInfo } from "@/http/timeline";
+import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
+
+import { pushAlert } from "@/services/alert";
import UserAvatar from "../common/user/UserAvatar";
import TimelineLine from "./TimelineLine";
+import TimelinePostContentView from "./TimelinePostContentView";
import TimelinePostDeleteConfirmDialog from "./TimelinePostDeleteConfirmDialog";
export interface TimelinePostViewProps {
@@ -13,10 +16,11 @@ export interface TimelinePostViewProps {
current?: boolean;
className?: string;
style?: React.CSSProperties;
+ onDeleted?: () => void;
}
const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
- const { post, className, style } = props;
+ const { post, className, style, onDeleted } = props;
const current = props.current === true;
const [
@@ -25,8 +29,6 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
] = React.useState<boolean>(false);
const [deleteDialog, setDeleteDialog] = React.useState<boolean>(false);
- // TODO: Load content.
-
return (
<div
className={clsx("timeline-item", current && "current", className)}
@@ -59,7 +61,9 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
</span>
</span>
</div>
- <div className="timeline-content">{/** TODO: Load content. */}</div>
+ <div className="timeline-content">
+ <TimelinePostContentView post={post} />
+ </div>
{operationMaskVisible ? (
<div
className="position-absolute position-lt w-100 h-100 mask d-flex justify-content-center align-items-center"
@@ -84,7 +88,17 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
setOperationMaskVisible(false);
}}
onConfirm={() => {
- // TODO: Implement this!
+ void getHttpTimelineClient()
+ .deletePost(post.timelineName, post.id)
+ .then(onDeleted, () => {
+ pushAlert({
+ type: "danger",
+ message: {
+ type: "i18n",
+ key: "timeline.deletePostFailed",
+ },
+ });
+ });
}}
/>
) : null}