diff --git a/web/src/components/FloatingNavButton.tsx b/web/src/components/FloatingNavButton.tsx index ae161575..a6c89b97 100644 --- a/web/src/components/FloatingNavButton.tsx +++ b/web/src/components/FloatingNavButton.tsx @@ -1,10 +1,10 @@ import { Dropdown, IconButton, Menu, MenuButton } from "@mui/joy"; -import { useNavigate } from "react-router-dom"; +import useNavigateTo from "@/hooks/useNavigateTo"; import { useTranslate } from "@/utils/i18n"; import Icon from "./Icon"; const FloatingNavButton = () => { - const navigate = useNavigate(); + const navigateTo = useNavigateTo(); const t = useTranslate(); return ( @@ -23,7 +23,7 @@ const FloatingNavButton = () => { diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index e148d87b..965b2938 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -6,6 +6,7 @@ import { Link } from "react-router-dom"; import { UNKNOWN_ID } from "@/helpers/consts"; import { getRelativeTimeString } from "@/helpers/datetime"; import useCurrentUser from "@/hooks/useCurrentUser"; +import useNavigateTo from "@/hooks/useNavigateTo"; import { useFilterStore, useMemoStore, useUserStore } from "@/store/module"; import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; @@ -17,7 +18,6 @@ import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog"; import MemoRelationListView from "./MemoRelationListView"; import MemoResourceListView from "./MemoResourceListView"; import showPreviewImageDialog from "./PreviewImageDialog"; -import showShareMemo from "./ShareMemoDialog"; import UserAvatar from "./UserAvatar"; import "@/less/memo.less"; @@ -30,6 +30,7 @@ interface Props { const Memo: React.FC = (props: Props) => { const { memo, lazyRendering } = props; const t = useTranslate(); + const navigateTo = useNavigateTo(); const { i18n } = useTranslation(); const filterStore = useFilterStore(); const userStore = useUserStore(); @@ -89,6 +90,14 @@ const Memo: React.FC = (props: Props) => { return
; } + const handleGotoMemoDetailPage = (event: React.MouseEvent) => { + if (event.altKey) { + showChangeMemoCreatedTsDialog(memo.id); + } else { + navigateTo(`/m/${memo.id}`); + } + }; + const handleTogglePinMemoBtnClick = async () => { try { if (memo.pinned) { @@ -143,10 +152,6 @@ const Memo: React.FC = (props: Props) => { }); }; - const handleGenerateMemoImageBtnClick = () => { - showShareMemo(memo); - }; - const handleMemoContentClick = async (e: React.MouseEvent) => { const targetEl = e.target as HTMLElement; @@ -217,11 +222,6 @@ const Memo: React.FC = (props: Props) => { handleEditMemoClick(); }; - const handleMemoCreatedTimeClick = (e: React.MouseEvent) => { - e.preventDefault(); - showChangeMemoCreatedTsDialog(memo.id); - }; - return ( <>
@@ -236,7 +236,7 @@ const Memo: React.FC = (props: Props) => { )} - + {displayTime}
@@ -256,10 +256,6 @@ const Memo: React.FC = (props: Props) => { {t("common.edit")} - - - {t("common.share")} - {t("common.mark")} diff --git a/web/src/components/ShareMemoDialog.tsx b/web/src/components/ShareMemoDialog.tsx index b24f10f4..6eb058a2 100644 --- a/web/src/components/ShareMemoDialog.tsx +++ b/web/src/components/ShareMemoDialog.tsx @@ -1,84 +1,55 @@ -import { Option, Select } from "@mui/joy"; +import { Button } from "@mui/joy"; import copy from "copy-to-clipboard"; -import { toLower } from "lodash-es"; import { QRCodeSVG } from "qrcode.react"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef } from "react"; import { toast } from "react-hot-toast"; -import { getMemoStats } from "@/helpers/api"; -import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts"; -import { getDateTimeString, getTimeStampByDate } from "@/helpers/datetime"; +import { getDateTimeString } from "@/helpers/datetime"; import useLoading from "@/hooks/useLoading"; import toImage from "@/labs/html2image"; -import { useMemoStore, useUserStore } from "@/store/module"; +import { useUserV1Store } from "@/store/v1"; import { useTranslate } from "@/utils/i18n"; import { generateDialog } from "./Dialog"; import showEmbedMemoDialog from "./EmbedMemoDialog"; import Icon from "./Icon"; import MemoContent from "./MemoContent"; import MemoResourceListView from "./MemoResourceListView"; +import UserAvatar from "./UserAvatar"; import "@/less/share-memo-dialog.less"; interface Props extends DialogProps { memo: Memo; } -interface State { - memoAmount: number; - memoVisibility: Visibility; - showQRCode: boolean; -} - const ShareMemoDialog: React.FC = (props: Props) => { const { memo: propsMemo, destroy } = props; const t = useTranslate(); - const userStore = useUserStore(); - const memoStore = useMemoStore(); - const user = userStore.state.user as User; - const [state, setState] = useState({ - memoAmount: 0, - memoVisibility: propsMemo.visibility, - showQRCode: true, - }); - const createLoadingState = useLoading(false); + const userV1Store = useUserV1Store(); + const downloadingImageState = useLoading(false); const loadingState = useLoading(); const memoElRef = useRef(null); const memo = { ...propsMemo, displayTsStr: getDateTimeString(propsMemo.displayTs), }; - const createdDays = Math.ceil((Date.now() - getTimeStampByDate(user.createdTs)) / 1000 / 3600 / 24); + const user = userV1Store.getUserByUsername(memo.creatorUsername); useEffect(() => { - getMemoStats(user.username) - .then(({ data }) => { - setPartialState({ - memoAmount: data.length, - }); - loadingState.setFinish(); - }) - .catch((error) => { - console.error(error); - }); + (async () => { + await userV1Store.getOrFetchUserByUsername(memo.creatorUsername); + loadingState.setFinish(); + })(); }, []); - const setPartialState = (partialState: Partial) => { - setState({ - ...state, - ...partialState, - }); - }; - const handleCloseBtnClick = () => { destroy(); }; - const handleDownloadBtnClick = () => { + const handleDownloadImageBtnClick = () => { if (!memoElRef.current) { return; } - createLoadingState.setLoading(); - + downloadingImageState.setLoading(); toImage(memoElRef.current, { pixelRatio: window.devicePixelRatio * 2, }) @@ -88,7 +59,7 @@ const ShareMemoDialog: React.FC = (props: Props) => { a.download = `memos-${getDateTimeString(Date.now())}.png`; a.click(); - createLoadingState.setFinish(); + downloadingImageState.setFinish(); }) .catch((err) => { console.error(err); @@ -104,23 +75,9 @@ const ShareMemoDialog: React.FC = (props: Props) => { toast.success(t("message.succeed-copy-link")); }; - const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => { - return { - value: item.value, - text: t(`memo.visibility.${toLower(item.value) as Lowercase}`), - }; - }); - - const handleMemoVisibilityOptionChanged = async (value: string) => { - const visibilityValue = value as Visibility; - setPartialState({ - memoVisibility: visibilityValue, - }); - await memoStore.patchMemo({ - id: memo.id, - visibility: visibilityValue, - }); - }; + if (loadingState.isLoading) { + return null; + } return ( <> @@ -131,41 +88,23 @@ const ShareMemoDialog: React.FC = (props: Props) => {
-
- {t("common.visibility")}: - -
- - + - + +
= (props: Props) => {
-
- -
+
{user.nickname || user.username} - - {state.memoAmount} MEMOS / {createdDays} DAYS -
{ const t = useTranslate(); - const navigate = useNavigate(); + const navigateTo = useNavigateTo(); const globalStore = useGlobalStore(); const userStore = useUserStore(); const { systemStatus } = globalStore.state; @@ -18,7 +18,7 @@ const UserBanner = () => { const title = user ? user.nickname : systemStatus.customizedProfile.name || "memos"; const handleMyAccountClick = () => { - navigate(`/u/${encodeURIComponent(user.username)}`); + navigateTo(`/u/${encodeURIComponent(user.username)}`); }; const handleAboutBtnClick = () => { diff --git a/web/src/hooks/index.ts b/web/src/hooks/index.ts index af1972e8..4e7282ac 100644 --- a/web/src/hooks/index.ts +++ b/web/src/hooks/index.ts @@ -1 +1,3 @@ export * from "./useLoading"; +export * from "./useCurrentUser"; +export * from "./useNavigateTo"; diff --git a/web/src/hooks/useNavigateTo.ts b/web/src/hooks/useNavigateTo.ts new file mode 100644 index 00000000..62e844fd --- /dev/null +++ b/web/src/hooks/useNavigateTo.ts @@ -0,0 +1,20 @@ +import { useNavigate } from "react-router-dom"; + +const useNavigateTo = () => { + const navigateTo = useNavigate(); + + const navigateToWithViewTransition = (to: string) => { + const document = window.document as any; + if (!document.startViewTransition) { + navigateTo(to); + } else { + document.startViewTransition(() => { + navigateTo(to); + }); + } + }; + + return navigateToWithViewTransition; +}; + +export default useNavigateTo; diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index 45544f4e..06958fbb 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -1,19 +1,30 @@ +import { Divider, Select, Tooltip, Option, IconButton } from "@mui/joy"; +import copy from "copy-to-clipboard"; +import { toLower } from "lodash-es"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useParams } from "react-router-dom"; import FloatingNavButton from "@/components/FloatingNavButton"; -import Memo from "@/components/Memo"; +import Icon from "@/components/Icon"; +import MemoContent from "@/components/MemoContent"; +import MemoRelationListView from "@/components/MemoRelationListView"; +import MemoResourceListView from "@/components/MemoResourceListView"; +import showShareMemoDialog from "@/components/ShareMemoDialog"; import UserAvatar from "@/components/UserAvatar"; -import useLoading from "@/hooks/useLoading"; +import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts"; +import { getDateTimeString } from "@/helpers/datetime"; +import useNavigateTo from "@/hooks/useNavigateTo"; import { useMemoStore } from "@/store/module"; import { useUserV1Store } from "@/store/v1"; import { User } from "@/types/proto/api/v2/user_service"; +import { useTranslate } from "@/utils/i18n"; const MemoDetail = () => { const params = useParams(); + const navigateTo = useNavigateTo(); + const t = useTranslate(); const memoStore = useMemoStore(); const userV1Store = useUserV1Store(); - const loadingState = useLoading(); const [user, setUser] = useState(); const memoId = Number(params.memoId); const memo = memoStore.state.memos.find((memo) => memo.id === memoId); @@ -25,18 +36,43 @@ const MemoDetail = () => { .then(async (memo) => { const user = await userV1Store.getOrFetchUserByUsername(memo.creatorUsername); setUser(user); - loadingState.setFinish(); }) .catch((error) => { console.error(error); toast.error(error.response.data.message); }); + } else { + navigateTo("/404"); } }, [memoId]); + if (!memo) { + return null; + } + + const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => { + return { + value: item.value, + text: t(`memo.visibility.${toLower(item.value) as Lowercase}`), + }; + }); + + const handleMemoVisibilityOptionChanged = async (value: string) => { + const visibilityValue = value as Visibility; + await memoStore.patchMemo({ + id: memo.id, + visibility: visibilityValue, + }); + }; + + const handleCopyLinkBtnClick = () => { + copy(`${window.location.origin}/m/${memo.id}`); + toast.success(t("message.succeed-copy-link")); + }; + return ( <> -
+
@@ -44,18 +80,48 @@ const MemoDetail = () => {

{user?.nickname}

- {!loadingState.isLoading && - (memo ? ( - <> -
- -
- - ) : ( - <> -

Not found

- - ))} +
+ + + + +
+
+ {getDateTimeString(memo.displayTs)} + · + + #{memo.id} + + · + + + +
+
+ + + + showShareMemoDialog(memo)}> + + +
+
+