From 6d47a07043cb24edf9cf1dc5f535730ac20c9691 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 12 Jan 2021 18:10:58 +0800
Subject: ...

---
 .../views/timeline-common/TimelinePageTemplate.tsx | 42 +++++++++-------------
 1 file changed, 17 insertions(+), 25 deletions(-)

(limited to 'FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx')

diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index f8b2b38b..e4d414b9 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -10,8 +10,10 @@ import { getHttpHighlightClient } from "@/http/highlight";
 
 import { TimelineMemberDialog } from "./TimelineMember";
 import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
-import { TimelinePageTemplateUIProps } from "./TimelinePageTemplateUI";
-import { TimelinePostSendCallback } from "./TimelinePostEdit";
+import {
+  TimelinePageTemplateData,
+  TimelinePageTemplateUIProps,
+} from "./TimelinePageTemplateUI";
 import { TimelinePostInfoEx } from "./Timeline";
 import { mergeDataStatus } from "@/services/DataHub2";
 
@@ -42,26 +44,6 @@ export default function TimelinePageTemplate<TManageItem>(
   const timelineAndStatus = useTimeline(name);
   const postsAndState = usePosts(name);
 
-  const onPost: TimelinePostSendCallback = React.useCallback(
-    (req) => {
-      return service.createPost(name, req).toPromise().then();
-    },
-    [service, name]
-  );
-
-  const onManageProp = props.onManage;
-
-  const onManage = React.useCallback(
-    (item: "property" | TManageItem) => {
-      if (item === "property") {
-        setDialog(item);
-      } else {
-        onManageProp(item);
-      }
-    },
-    [onManageProp]
-  );
-
   const data = ((): TimelinePageTemplateUIProps<TManageItem>["data"] => {
     const { status, data: timeline } = timelineAndStatus;
     if (timeline == null) {
@@ -98,10 +80,20 @@ export default function TimelinePageTemplate<TManageItem>(
         }
       })();
 
-      const operations = {
-        onPost: service.hasPostPermission(user, timeline) ? onPost : undefined,
+      const operations: TimelinePageTemplateData<TManageItem>["operations"] = {
+        onPost: service.hasPostPermission(user, timeline)
+          ? (req) => {
+              return service.createPost(name, req).toPromise().then();
+            }
+          : undefined,
         onManage: service.hasManagePermission(user, timeline)
-          ? onManage
+          ? (item) => {
+              if (item === "property") {
+                setDialog(item);
+              } else {
+                props.onManage(item);
+              }
+            }
           : undefined,
         onMember: () => setDialog("member"),
         onBookmark:
-- 
cgit v1.2.3


From 2bb2377601c44ab406fade8e7bfdc888900abe7f Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 12 Jan 2021 22:01:51 +0800
Subject: ...

---
 FrontEnd/src/app/services/timeline.ts              | 15 ++++++--------
 .../views/timeline-common/TimelinePageTemplate.tsx | 10 ++--------
 .../TimelinePropertyChangeDialog.tsx               | 23 ++++++++++------------
 3 files changed, 18 insertions(+), 30 deletions(-)

(limited to 'FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx')

diff --git a/FrontEnd/src/app/services/timeline.ts b/FrontEnd/src/app/services/timeline.ts
index 7d239fbf..4e2530cc 100644
--- a/FrontEnd/src/app/services/timeline.ts
+++ b/FrontEnd/src/app/services/timeline.ts
@@ -175,15 +175,12 @@ export class TimelineService {
   changeTimelineProperty(
     timelineName: string,
     req: TimelineChangePropertyRequest
-  ): Observable<TimelineInfo> {
-    return from(
-      getHttpTimelineClient()
-        .patchTimeline(timelineName, req)
-        .then((timeline) => {
-          void this.syncTimeline(timelineName);
-          return timeline;
-        })
-    );
+  ): Promise<void> {
+    return getHttpTimelineClient()
+      .patchTimeline(timelineName, req)
+      .then(() => {
+        void this.syncTimeline(timelineName);
+      });
   }
 
   deleteTimeline(timelineName: string): Observable<unknown> {
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index e4d414b9..8422077a 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -173,14 +173,8 @@ export default function TimelinePageTemplate<TManageItem>(
       <TimelinePropertyChangeDialog
         open
         close={closeDialog}
-        oldInfo={{
-          title: timeline.title,
-          visibility: timeline.visibility,
-          description: timeline.description,
-        }}
-        onProcess={(req) => {
-          return service.changeTimelineProperty(name, req).toPromise().then();
-        }}
+        timeline={timeline}
+        onProcess={(req) => service.changeTimelineProperty(name, req)}
       />
     );
   } else if (dialog === "member") {
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx
index aae227e6..ab3285f5 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx
@@ -4,20 +4,15 @@ import {
   TimelineVisibility,
   kTimelineVisibilities,
   TimelineChangePropertyRequest,
+  TimelineInfo,
 } from "@/services/timeline";
 
 import OperationDialog from "../common/OperationDialog";
 
-export interface TimelinePropertyInfo {
-  title: string;
-  visibility: TimelineVisibility;
-  description: string;
-}
-
 export interface TimelinePropertyChangeDialogProps {
   open: boolean;
   close: () => void;
-  oldInfo: TimelinePropertyInfo;
+  timeline: TimelineInfo;
   onProcess: (request: TimelineChangePropertyRequest) => Promise<void>;
 }
 
@@ -30,6 +25,8 @@ const labelMap: { [key in TimelineVisibility]: string } = {
 const TimelinePropertyChangeDialog: React.FC<TimelinePropertyChangeDialogProps> = (
   props
 ) => {
+  const { timeline } = props;
+
   return (
     <OperationDialog
       title={"timeline.dialogChangeProperty.title"}
@@ -37,7 +34,7 @@ const TimelinePropertyChangeDialog: React.FC<TimelinePropertyChangeDialogProps>
         {
           type: "text",
           label: "timeline.dialogChangeProperty.titleField",
-          initValue: props.oldInfo.title,
+          initValue: timeline.title,
         },
         {
           type: "select",
@@ -46,25 +43,25 @@ const TimelinePropertyChangeDialog: React.FC<TimelinePropertyChangeDialogProps>
             label: labelMap[v],
             value: v,
           })),
-          initValue: props.oldInfo.visibility,
+          initValue: timeline.visibility,
         },
         {
           type: "text",
           label: "timeline.dialogChangeProperty.description",
-          initValue: props.oldInfo.description,
+          initValue: timeline.description,
         },
       ]}
       open={props.open}
       close={props.close}
       onProcess={([newTitle, newVisibility, newDescription]) => {
         const req: TimelineChangePropertyRequest = {};
-        if (newTitle !== props.oldInfo.title) {
+        if (newTitle !== timeline.title) {
           req.title = newTitle;
         }
-        if (newVisibility !== props.oldInfo.visibility) {
+        if (newVisibility !== timeline.visibility) {
           req.visibility = newVisibility as TimelineVisibility;
         }
-        if (newDescription !== props.oldInfo.description) {
+        if (newDescription !== timeline.description) {
           req.description = newDescription;
         }
         return props.onProcess(req);
-- 
cgit v1.2.3


From 36515dde9ddaec074b68492a6e220bc4ed3d70af Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 12 Jan 2021 22:09:34 +0800
Subject: ...

---
 FrontEnd/src/app/services/timeline.ts              | 50 +++++++++++++-------
 .../views/timeline-common/TimelinePageTemplate.tsx | 54 +++++++---------------
 2 files changed, 50 insertions(+), 54 deletions(-)

(limited to 'FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx')

diff --git a/FrontEnd/src/app/services/timeline.ts b/FrontEnd/src/app/services/timeline.ts
index 4e2530cc..46671ea1 100644
--- a/FrontEnd/src/app/services/timeline.ts
+++ b/FrontEnd/src/app/services/timeline.ts
@@ -26,6 +26,8 @@ export type { TimelineVisibility } from "@/http/timeline";
 import { dataStorage } from "./common";
 import { userInfoService, AuthUser } from "./user";
 import { DataAndStatus, DataHub2 } from "./DataHub2";
+import { getHttpBookmarkClient } from "@/http/bookmark";
+import { getHttpHighlightClient } from "@/http/highlight";
 
 export type TimelineInfo = HttpTimelineInfo;
 export type TimelineChangePropertyRequest = HttpTimelinePatchRequest;
@@ -347,24 +349,20 @@ export class TimelineService {
   createPost(
     timelineName: string,
     request: TimelineCreatePostRequest
-  ): Observable<unknown> {
-    return from(
-      getHttpTimelineClient()
-        .postPost(timelineName, request)
-        .then(() => {
-          void this.syncPosts(timelineName);
-        })
-    );
+  ): Promise<void> {
+    return getHttpTimelineClient()
+      .postPost(timelineName, request)
+      .then(() => {
+        void this.syncPosts(timelineName);
+      });
   }
 
-  deletePost(timelineName: string, postId: number): Observable<unknown> {
-    return from(
-      getHttpTimelineClient()
-        .deletePost(timelineName, postId)
-        .then(() => {
-          void this.syncPosts(timelineName);
-        })
-    );
+  deletePost(timelineName: string, postId: number): Promise<void> {
+    return getHttpTimelineClient()
+      .deletePost(timelineName, postId)
+      .then(() => {
+        void this.syncPosts(timelineName);
+      });
   }
 
   isMemberOf(username: string, timeline: TimelineInfo): boolean {
@@ -433,6 +431,26 @@ export class TimelineService {
         user.username === post.author.username)
     );
   }
+
+  setHighlight(timelineName: string, highlight: boolean): Promise<void> {
+    const client = getHttpHighlightClient();
+    const promise = highlight
+      ? client.put(timelineName)
+      : client.delete(timelineName);
+    return promise.then(() => {
+      void timelineService.syncTimeline(timelineName);
+    });
+  }
+
+  setBookmark(timelineName: string, bookmark: boolean): Promise<void> {
+    const client = getHttpBookmarkClient();
+    const promise = bookmark
+      ? client.put(timelineName)
+      : client.delete(timelineName);
+    return promise.then(() => {
+      void timelineService.syncTimeline(timelineName);
+    });
+  }
 }
 
 export const timelineService = new TimelineService();
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index 8422077a..5c87756c 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -5,8 +5,6 @@ import { UiLogicError } from "@/common";
 import { pushAlert } from "@/services/alert";
 import { useUser } from "@/services/user";
 import { timelineService, usePosts, useTimeline } from "@/services/timeline";
-import { getHttpBookmarkClient } from "@/http/bookmark";
-import { getHttpHighlightClient } from "@/http/highlight";
 
 import { TimelineMemberDialog } from "./TimelineMember";
 import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
@@ -66,13 +64,11 @@ export default function TimelinePageTemplate<TManageItem>(
             ...post,
             onDelete: service.hasModifyPostPermission(user, timeline, post)
               ? () => {
-                  service.deletePost(name, post.id).subscribe({
-                    error: () => {
-                      pushAlert({
-                        type: "danger",
-                        message: t("timeline.deletePostFailed"),
-                      });
-                    },
+                  service.deletePost(name, post.id).catch(() => {
+                    pushAlert({
+                      type: "danger",
+                      message: t("timeline.deletePostFailed"),
+                    });
                   });
                 }
               : undefined,
@@ -82,9 +78,7 @@ export default function TimelinePageTemplate<TManageItem>(
 
       const operations: TimelinePageTemplateData<TManageItem>["operations"] = {
         onPost: service.hasPostPermission(user, timeline)
-          ? (req) => {
-              return service.createPost(name, req).toPromise().then();
-            }
+          ? (req) => service.createPost(name, req)
           : undefined,
         onManage: service.hasManagePermission(user, timeline)
           ? (item) => {
@@ -99,53 +93,37 @@ export default function TimelinePageTemplate<TManageItem>(
         onBookmark:
           user != null
             ? () => {
-                const { isBookmark } = timeline;
-                const client = getHttpBookmarkClient();
-                const promise = isBookmark
-                  ? client.delete(name)
-                  : client.put(name);
-                promise.then(
-                  () => {
-                    void timelineService.syncTimeline(name);
-                  },
-                  () => {
+                service
+                  .setBookmark(timeline.name, !timeline.isBookmark)
+                  .catch(() => {
                     pushAlert({
                       message: {
                         type: "i18n",
-                        key: isBookmark
+                        key: timeline.isBookmark
                           ? "timeline.removeBookmarkFail"
                           : "timeline.addBookmarkFail",
                       },
                       type: "danger",
                     });
-                  }
-                );
+                  });
               }
             : undefined,
         onHighlight:
           user != null && user.hasHighlightTimelineAdministrationPermission
             ? () => {
-                const { isHighlight } = timeline;
-                const client = getHttpHighlightClient();
-                const promise = isHighlight
-                  ? client.delete(name)
-                  : client.put(name);
-                promise.then(
-                  () => {
-                    void timelineService.syncTimeline(name);
-                  },
-                  () => {
+                service
+                  .setHighlight(timeline.name, !timeline.isHighlight)
+                  .catch(() => {
                     pushAlert({
                       message: {
                         type: "i18n",
-                        key: isHighlight
+                        key: timeline.isHighlight
                           ? "timeline.removeHighlightFail"
                           : "timeline.addHighlightFail",
                       },
                       type: "danger",
                     });
-                  }
-                );
+                  });
               }
             : undefined,
       };
-- 
cgit v1.2.3


From 3a1f46118e81c5a6206a3c0375f275ee7f369200 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 12 Jan 2021 22:10:47 +0800
Subject: ...

---
 FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

(limited to 'FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx')

diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index 5c87756c..5833c541 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -132,13 +132,9 @@ export default function TimelinePageTemplate<TManageItem>(
     }
   })();
 
-  const closeDialog = React.useCallback((): void => {
-    setDialog(null);
-  }, []);
-
-  let dialogElement: React.ReactElement | undefined;
-
   const timeline = timelineAndStatus?.data;
+  let dialogElement: React.ReactElement | undefined;
+  const closeDialog = (): void => setDialog(null);
 
   if (dialog === "property") {
     if (timeline == null || timeline === "notexist") {
-- 
cgit v1.2.3


From e6dff0d19d524d14a3adff7803d9a56264e85f2e Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 12 Jan 2021 23:47:08 +0800
Subject: fix: Fix #205.

---
 .../views/timeline-common/TimelinePageTemplate.tsx | 34 ++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

(limited to 'FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx')

diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index 5833c541..fc4c52ec 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -5,6 +5,7 @@ import { UiLogicError } from "@/common";
 import { pushAlert } from "@/services/alert";
 import { useUser } from "@/services/user";
 import { timelineService, usePosts, useTimeline } from "@/services/timeline";
+import { mergeDataStatus } from "@/services/DataHub2";
 
 import { TimelineMemberDialog } from "./TimelineMember";
 import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
@@ -13,7 +14,6 @@ import {
   TimelinePageTemplateUIProps,
 } from "./TimelinePageTemplateUI";
 import { TimelinePostInfoEx } from "./Timeline";
-import { mergeDataStatus } from "@/services/DataHub2";
 
 export interface TimelinePageTemplateProps<TManageItem> {
   name: string;
@@ -39,9 +39,38 @@ export default function TimelinePageTemplate<TManageItem>(
     null
   );
 
+  const [scrollBottomKey, setScrollBottomKey] = React.useState<number>(0);
+
+  React.useEffect(() => {
+    if (scrollBottomKey > 0) {
+      window.scrollTo(0, document.body.scrollHeight);
+    }
+  }, [scrollBottomKey]);
+
   const timelineAndStatus = useTimeline(name);
   const postsAndState = usePosts(name);
 
+  const [
+    scrollToBottomNextSyncKey,
+    setScrollToBottomNextSyncKey,
+  ] = React.useState<number>(0);
+
+  const scrollToBottomNextSync = (): void => {
+    setScrollToBottomNextSyncKey((old) => old + 1);
+  };
+
+  React.useEffect(() => {
+    let subscribe = true;
+    void timelineService.syncPosts(name).then(() => {
+      if (subscribe) {
+        setScrollBottomKey((old) => old + 1);
+      }
+    });
+    return () => {
+      subscribe = false;
+    };
+  }, [name, scrollToBottomNextSyncKey]);
+
   const data = ((): TimelinePageTemplateUIProps<TManageItem>["data"] => {
     const { status, data: timeline } = timelineAndStatus;
     if (timeline == null) {
@@ -78,7 +107,8 @@ export default function TimelinePageTemplate<TManageItem>(
 
       const operations: TimelinePageTemplateData<TManageItem>["operations"] = {
         onPost: service.hasPostPermission(user, timeline)
-          ? (req) => service.createPost(name, req)
+          ? (req) =>
+              service.createPost(name, req).then(() => scrollToBottomNextSync())
           : undefined,
         onManage: service.hasManagePermission(user, timeline)
           ? (item) => {
-- 
cgit v1.2.3

