From ded4da07a3eed4becf42edc7605321a77ac6a662 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 13 Mar 2024 23:47:34 +0800 Subject: [PATCH] feat: use @github/relative-time-element to display time --- web/package.json | 1 + web/pnpm-lock.yaml | 7 ++ web/src/components/BetaBadge.tsx | 22 ----- web/src/components/MemoActionMenu.tsx | 19 +++-- .../EmbeddedContent/EmbeddedMemo.tsx | 5 +- web/src/components/MemoContent/index.tsx | 2 +- web/src/components/MemoView.tsx | 67 ++++++--------- web/src/helpers/datetime.ts | 84 ------------------- web/src/main.tsx | 1 + web/src/pages/Archived.tsx | 5 +- web/src/pages/MemoDetail.tsx | 9 +- 11 files changed, 64 insertions(+), 158 deletions(-) delete mode 100644 web/src/components/BetaBadge.tsx diff --git a/web/package.json b/web/package.json index c67f5e77..685580cc 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@github/relative-time-element": "^4.3.1", "@matejmazur/react-katex": "^3.1.3", "@mui/joy": "5.0.0-beta.30", "@reduxjs/toolkit": "^1.9.7", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index bf45dc06..a0aff3c8 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@emotion/styled': specifier: ^11.11.0 version: 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.63)(react@18.2.0) + '@github/relative-time-element': + specifier: ^4.3.1 + version: 4.3.1 '@matejmazur/react-katex': specifier: ^3.1.3 version: 3.1.3(katex@0.16.9)(react@18.2.0) @@ -905,6 +908,10 @@ packages: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: false + /@github/relative-time-element@4.3.1: + resolution: {integrity: sha512-zL79nlhZVCg7x2Pf/HT5MB0mowmErE71VXpF10/3Wy8dQwkninNO1M9aOizh2wKC5LkSpDXqNYjDZwbH0/bcSg==} + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} diff --git a/web/src/components/BetaBadge.tsx b/web/src/components/BetaBadge.tsx deleted file mode 100644 index 26822674..00000000 --- a/web/src/components/BetaBadge.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useTranslate } from "@/utils/i18n"; - -interface Props { - className?: string; -} - -const BetaBadge: React.FC = (props: Props) => { - const { className } = props; - const t = useTranslate(); - - return ( - - {t("common.beta")} - - ); -}; - -export default BetaBadge; diff --git a/web/src/components/MemoActionMenu.tsx b/web/src/components/MemoActionMenu.tsx index c122795c..201265f3 100644 --- a/web/src/components/MemoActionMenu.tsx +++ b/web/src/components/MemoActionMenu.tsx @@ -1,7 +1,9 @@ import { Dropdown, Menu, MenuButton, MenuItem } from "@mui/joy"; import classNames from "classnames"; import toast from "react-hot-toast"; +import { useLocation } from "react-router-dom"; import Icon from "@/components/Icon"; +import useNavigateTo from "@/hooks/useNavigateTo"; import { useMemoStore } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v2/common"; import { Memo } from "@/types/proto/api/v2/memo_service"; @@ -14,14 +16,15 @@ interface Props { memo: Memo; className?: string; hiddenActions?: ("edit" | "archive" | "delete" | "share" | "pin")[]; - onArchived?: () => void; - onDeleted?: () => void; } const MemoActionMenu = (props: Props) => { const { memo, hiddenActions } = props; const t = useTranslate(); + const location = useLocation(); + const navigateTo = useNavigateTo(); const memoStore = useMemoStore(); + const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.name}`); const handleTogglePinMemoBtnClick = async () => { try { @@ -66,9 +69,12 @@ const MemoActionMenu = (props: Props) => { } catch (error: any) { console.error(error); toast.error(error.response.data.message); + return; } - if (props.onArchived) { - props.onArchived(); + + toast.success("Archived successfully"); + if (isInMemoDetailPage) { + navigateTo("/archived"); } }; @@ -80,8 +86,9 @@ const MemoActionMenu = (props: Props) => { dialogName: "delete-memo-dialog", onConfirm: async () => { await memoStore.deleteMemo(memo.id); - if (props.onDeleted) { - props.onDeleted(); + toast.success("Deleted successfully"); + if (isInMemoDetailPage) { + navigateTo("/"); } }, }); diff --git a/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx b/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx index a3cc4a9a..af510807 100644 --- a/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx +++ b/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx @@ -2,7 +2,6 @@ import { useContext, useEffect } from "react"; import { Link } from "react-router-dom"; import Icon from "@/components/Icon"; import MemoResourceListView from "@/components/MemoResourceListView"; -import { getDateTimeString } from "@/helpers/datetime"; import useLoading from "@/hooks/useLoading"; import { useMemoStore } from "@/store/v1"; import MemoContent from ".."; @@ -51,7 +50,9 @@ const EmbeddedMemo = ({ resourceId, params: paramsStr }: Props) => { return (
- {getDateTimeString(memo.displayTime)} +
+ +
diff --git a/web/src/components/MemoContent/index.tsx b/web/src/components/MemoContent/index.tsx index 09ed45b9..5a795b5e 100644 --- a/web/src/components/MemoContent/index.tsx +++ b/web/src/components/MemoContent/index.tsx @@ -74,7 +74,7 @@ const MemoContent: React.FC = (props: Props) => {
= (props: Props) => { const { memo, className } = props; const t = useTranslate(); + const location = useLocation(); const navigateTo = useNavigateTo(); - const { i18n } = useTranslation(); const currentUser = useCurrentUser(); const userStore = useUserStore(); const user = useCurrentUser(); - const [displayTime, setDisplayTime] = useState(getRelativeTimeString(getTimeStampByDate(memo.displayTime))); const [creator, setCreator] = useState(userStore.getUserByUsername(extractUsernameFromName(memo.creator))); const memoContainerRef = useRef(null); const referencedMemos = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE); @@ -46,6 +44,7 @@ const MemoView: React.FC = (props: Props) => { (relation) => relation.type === MemoRelation_Type.COMMENT && relation.relatedMemoId === memo.id, ).length; const readonly = memo.creator !== user?.name; + const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.name}`); // Initial related data: creator. useEffect(() => { @@ -55,20 +54,6 @@ const MemoView: React.FC = (props: Props) => { })(); }, []); - // Update display time string. - useEffect(() => { - let intervalFlag: any = -1; - if (Date.now() - getTimeStampByDate(memo.displayTime) < 1000 * 60 * 60 * 24) { - intervalFlag = setInterval(() => { - setDisplayTime(getRelativeTimeString(getTimeStampByDate(memo.displayTime))); - }, 1000 * 1); - } - - return () => { - clearInterval(intervalFlag); - }; - }, [i18n.language]); - const handleGotoMemoDetailPage = (event: React.MouseEvent) => { if (event.altKey) { showChangeMemoCreatedTsDialog(memo.id); @@ -124,8 +109,8 @@ const MemoView: React.FC = (props: Props) => {
)}
-
-
+
+
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( @@ -133,25 +118,27 @@ const MemoView: React.FC = (props: Props) => { )} - {currentUser && } + {currentUser && }
- - - {commentAmount > 0 && {commentAmount}} - + {!isInMemoDetailPage && ( + + + {commentAmount > 0 && {commentAmount}} + + )} {props.showPinned && memo.pinned && ( - + )} - {!readonly && } + {!readonly && }
= (props: Props) => { content={memo.content} readonly={readonly} onClick={handleMemoContentClick} - compact={true} + compact={props.compact ?? true} /> -
- - {displayTime} - +
+
+ +
diff --git a/web/src/helpers/datetime.ts b/web/src/helpers/datetime.ts index 54d57158..453e5fc1 100644 --- a/web/src/helpers/datetime.ts +++ b/web/src/helpers/datetime.ts @@ -4,13 +4,6 @@ export function getTimeStampByDate(t: Date | number | string | any): number { return new Date(t).getTime(); } -export function getDateStampByDate(t?: Date | number | string): number { - const tsFromDate = getTimeStampByDate(t ? t : Date.now()); - const d = new Date(tsFromDate); - - return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); -} - /** * Get a time string to provided time. * @@ -57,56 +50,6 @@ export function getDateTimeString(t?: Date | number | string | any, locale = i18 } } -/** - * Get a localized relative time string to provided time. - * - * Possible outputs for "long" format and "en" locale: - * - "x seconds ago" - * - "x minutes ago" - * - "x hours ago" - * - "yesterday" - * - "x days ago" - * - "x weeks ago" - * - "x months ago" - * - "last year" - * - "x years ago" - */ -export const getRelativeTimeString = (time: number, locale = i18n.language, formatStyle: "long" | "short" | "narrow" = "long"): string => { - const pastTimeMillis = Date.now() - time; - const secMillis = 1000; - const minMillis = secMillis * 60; - const hourMillis = minMillis * 60; - const dayMillis = hourMillis * 24; - // Show full date if more than 1 day ago. - if (pastTimeMillis >= dayMillis) { - return getDateTimeString(time, locale); - } - - // numeric: "auto" provides "yesterday" for 1 day ago, "always" provides "1 day ago" - const formatOpts = { style: formatStyle, numeric: "auto" } as Intl.RelativeTimeFormatOptions; - const relTime = new Intl.RelativeTimeFormat(locale, formatOpts); - if (pastTimeMillis < minMillis) { - return relTime.format(-Math.round(pastTimeMillis / secMillis), "second"); - } - if (pastTimeMillis < hourMillis) { - return relTime.format(-Math.round(pastTimeMillis / minMillis), "minute"); - } - if (pastTimeMillis < dayMillis) { - return relTime.format(-Math.round(pastTimeMillis / hourMillis), "hour"); - } - if (pastTimeMillis < dayMillis * 7) { - return relTime.format(-Math.round(pastTimeMillis / dayMillis), "day"); - } - if (pastTimeMillis < dayMillis * 30) { - return relTime.format(-Math.round(pastTimeMillis / (dayMillis * 7)), "week"); - } - if (pastTimeMillis < dayMillis * 365) { - return relTime.format(-Math.round(pastTimeMillis / (dayMillis * 30)), "month"); - } - - return relTime.format(-Math.round(pastTimeMillis / (dayMillis * 365)), "year"); -}; - /** * This returns the normalized date string of the provided date. * Format is always `YYYY-MM-DDT00:00`. @@ -143,33 +86,6 @@ export function getNormalizedDateString(t?: Date | number | string): string { return `${yyyy}-${MM}-${dd}`; } -/** - * This returns the Unix timestamp (the number of **seconds** since the Unix Epoch) of the provided date. - * - * If no date is provided, the current date is used. - * ``` - * getUnixTime("2019-01-25 00:00") // 1548381600 - * ``` - * This value is floored to the nearest second, and does not include a milliseconds component. - */ -export function getUnixTime(t?: Date | number | string): number { - const date = new Date(t ? t : Date.now()); - return Math.floor(date.getTime() / 1000); -} - -/** - * Checks if the provided date or timestamp is in the future. - * - * If no date is provided, the current date is used. - * - * @param t - Date or timestamp to check. - * @returns `true` if the date is in the future, `false` otherwise. - */ -export function isFutureDate(t?: Date | number | string): boolean { - const timestamp = getTimeStampByDate(t ? t : Date.now()); - return timestamp > Date.now(); -} - /** * Calculates a new Date object by adjusting the provided date, timestamp, or date string * based on the current timezone offset. diff --git a/web/src/main.tsx b/web/src/main.tsx index d33201a5..21e08e7f 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,3 +1,4 @@ +import "@github/relative-time-element"; import { CssVarsProvider } from "@mui/joy"; import { createRoot } from "react-dom/client"; import { Toaster } from "react-hot-toast"; diff --git a/web/src/pages/Archived.tsx b/web/src/pages/Archived.tsx index a7014503..9872a0b0 100644 --- a/web/src/pages/Archived.tsx +++ b/web/src/pages/Archived.tsx @@ -11,7 +11,6 @@ import MobileHeader from "@/components/MobileHeader"; import SearchBar from "@/components/SearchBar"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { getTimeStampByDate } from "@/helpers/datetime"; -import { getDateTimeString } from "@/helpers/datetime"; import useCurrentUser from "@/hooks/useCurrentUser"; import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams"; import { useMemoList, useMemoStore } from "@/store/v1"; @@ -105,7 +104,9 @@ const Archived = () => { >
- {getDateTimeString(memo.displayTime)} +
+ +
diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index 3dee3c4b..b0a09f0e 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -81,7 +81,14 @@ const MemoDetail = () => {
)} - +

Comments