aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FrontEnd/src/app/http/bookmark.ts70
-rw-r--r--FrontEnd/src/app/index.sass9
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx14
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx4
-rw-r--r--FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx12
-rw-r--r--FrontEnd/src/app/views/user/UserInfoCard.tsx12
6 files changed, 117 insertions, 4 deletions
diff --git a/FrontEnd/src/app/http/bookmark.ts b/FrontEnd/src/app/http/bookmark.ts
new file mode 100644
index 00000000..68de4d73
--- /dev/null
+++ b/FrontEnd/src/app/http/bookmark.ts
@@ -0,0 +1,70 @@
+import axios from "axios";
+
+import {
+ apiBaseUrl,
+ convertToNetworkError,
+ extractResponseData,
+} from "./common";
+
+import {
+ HttpTimelineInfo,
+ processRawTimelineInfo,
+ RawHttpTimelineInfo,
+} from "./timeline";
+
+export interface HttpHighlightMoveRequest {
+ timeline: string;
+ newPosition: number;
+}
+
+export interface IHttpBookmarkClient {
+ list(token: string): Promise<HttpTimelineInfo[]>;
+ put(timeline: string, token: string): Promise<void>;
+ delete(timeline: string, token: string): Promise<void>;
+ move(req: HttpHighlightMoveRequest, token: string): Promise<void>;
+}
+
+export class HttpHighlightClient implements IHttpBookmarkClient {
+ list(token: string): Promise<HttpTimelineInfo[]> {
+ return axios
+ .get<RawHttpTimelineInfo[]>(`${apiBaseUrl}/bookmarks?token=${token}`)
+ .then(extractResponseData)
+ .then((list) => list.map(processRawTimelineInfo))
+ .catch(convertToNetworkError);
+ }
+
+ put(timeline: string, token: string): Promise<void> {
+ return axios
+ .put(`${apiBaseUrl}/bookmarks/${timeline}?token=${token}`)
+ .catch(convertToNetworkError)
+ .then();
+ }
+
+ delete(timeline: string, token: string): Promise<void> {
+ return axios
+ .delete(`${apiBaseUrl}/bookmarks/${timeline}?token=${token}`)
+ .catch(convertToNetworkError)
+ .then();
+ }
+
+ move(req: HttpHighlightMoveRequest, token: string): Promise<void> {
+ return axios
+ .post(`${apiBaseUrl}/bookmarkop/move?token=${token}`, req)
+ .catch(convertToNetworkError)
+ .then();
+ }
+}
+
+let client: IHttpBookmarkClient = new HttpHighlightClient();
+
+export function getHttpBookmarkClient(): IHttpBookmarkClient {
+ return client;
+}
+
+export function setHttpBookmarkClient(
+ newClient: IHttpBookmarkClient
+): IHttpBookmarkClient {
+ const old = client;
+ client = newClient;
+ return old;
+}
diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass
index 6325895a..ae315715 100644
--- a/FrontEnd/src/app/index.sass
+++ b/FrontEnd/src/app/index.sass
@@ -34,10 +34,12 @@ small
width: 40px
.icon-button
- font-size: 1.4em
+ width: 1.4em
+ height: 1.4em
cursor: pointer
&.large
- font-size: 1.6em
+ width: 1.6em
+ height: 1.6em
.cursor-pointer
cursor: pointer
@@ -69,6 +71,9 @@ textarea
.text-orange
color: $orange
+.text-yellow
+ color: $yellow
+
@each $color, $value in $theme-colors
.text-button
background: transparent
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
index 6c57e91d..3833cdd9 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx
@@ -11,6 +11,7 @@ import {
usePostList,
useTimelineInfo,
} from "@/services/timeline";
+import { getHttpBookmarkClient } from "@/http/bookmark";
import { TimelineMemberDialog } from "./TimelineMember";
import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
@@ -116,6 +117,19 @@ export default function TimelinePageTemplate<TManageItem>(
? onManage
: undefined,
onMember: () => setDialog("member"),
+ onBookmark:
+ user != null
+ ? () => {
+ void getHttpBookmarkClient()
+ .put(name, user.token)
+ .then(() => {
+ pushAlert({
+ message: "Succeeded to add bookmark!", //TODO: i18n
+ type: "success",
+ });
+ });
+ }
+ : undefined,
};
if (type === "cache") {
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
index f60383dd..b7cd4a45 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx
@@ -15,6 +15,8 @@ export interface TimelineCardComponentProps<TManageItems> {
timeline: TimelineInfo;
onManage?: (item: TManageItems | "property") => void;
onMember: () => void;
+ onBookmark?: () => void;
+ onHighlight?: () => void;
className?: string;
collapse: boolean;
syncStatus: TimelineSyncStatus;
@@ -28,6 +30,7 @@ export interface TimelinePageTemplateUIProps<TManageItems> {
posts?: TimelinePostInfoEx[];
onManage?: (item: TManageItems | "property") => void;
onMember: () => void;
+ onBookmark?: () => void;
onPost?: TimelinePostSendCallback;
}
| I18nText;
@@ -153,6 +156,7 @@ export default function TimelinePageTemplateUI<TManageItems>(
timeline={data.timeline}
onManage={data.onManage}
onMember={data.onMember}
+ onBookmark={data.onBookmark}
syncStatus={syncStatus}
collapse={cardCollapse}
toggleCollapse={toggleCardCollapse}
diff --git a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx
index 934ad090..8f967a34 100644
--- a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx
+++ b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx
@@ -1,6 +1,8 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Dropdown, Button } from "react-bootstrap";
+import Svg from "react-inlinesvg";
+import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg";
import { useAvatar } from "@/services/user";
import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline";
@@ -18,6 +20,7 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => {
timeline,
collapse,
onMember,
+ onBookmark,
onManage,
syncStatus,
toggleCollapse,
@@ -50,8 +53,15 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => {
{t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
</small>
<div className="text-right mt-2">
+ {onBookmark != null ? (
+ <Svg
+ src={bookmarkIcon}
+ className="icon-button text-yellow mr-3"
+ onClick={onBookmark}
+ />
+ ) : null}
{onManage != null ? (
- <Dropdown>
+ <Dropdown className="d-inline-block">
<Dropdown.Toggle variant="outline-primary">
{t("timeline.manage")}
</Dropdown.Toggle>
diff --git a/FrontEnd/src/app/views/user/UserInfoCard.tsx b/FrontEnd/src/app/views/user/UserInfoCard.tsx
index 3ba1c96e..0e1e093a 100644
--- a/FrontEnd/src/app/views/user/UserInfoCard.tsx
+++ b/FrontEnd/src/app/views/user/UserInfoCard.tsx
@@ -1,6 +1,8 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Dropdown, Button } from "react-bootstrap";
+import Svg from "react-inlinesvg";
+import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg";
import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline";
import { useAvatar } from "@/services/user";
@@ -19,6 +21,7 @@ const UserInfoCard: React.FC<UserInfoCardProps> = (props) => {
collapse,
onMember,
onManage,
+ onBookmark,
syncStatus,
toggleCollapse,
} = props;
@@ -46,8 +49,15 @@ const UserInfoCard: React.FC<UserInfoCardProps> = (props) => {
{t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
</small>
<div className="text-right mt-2">
+ {onBookmark != null ? (
+ <Svg
+ src={bookmarkIcon}
+ className="icon-button text-yellow mr-3"
+ onClick={onBookmark}
+ />
+ ) : null}
{onManage != null ? (
- <Dropdown>
+ <Dropdown className="d-inline-block">
<Dropdown.Toggle variant="outline-primary">
{t("timeline.manage")}
</Dropdown.Toggle>