diff options
-rw-r--r-- | FrontEnd/src/app/http/bookmark.ts | 70 | ||||
-rw-r--r-- | FrontEnd/src/app/index.sass | 9 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx | 14 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx | 12 | ||||
-rw-r--r-- | FrontEnd/src/app/views/user/UserInfoCard.tsx | 12 |
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> |