import { Divider, Tooltip } from "@mui/joy"; import classNames from "classnames"; import copy from "copy-to-clipboard"; import { memo, useCallback, useEffect, useRef, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { UNKNOWN_ID } from "@/helpers/consts"; import { getRelativeTimeString, getTimeStampByDate } from "@/helpers/datetime"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; import { useUserStore, extractUsernameFromName, useMemoStore } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v2/common"; import { MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service"; import { Memo, Visibility } from "@/types/proto/api/v2/memo_service"; import { useTranslate } from "@/utils/i18n"; import { convertVisibilityToString } from "@/utils/memo"; import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; import { showCommonDialog } from "./Dialog/CommonDialog"; import Icon from "./Icon"; import MemoContent from "./MemoContent"; import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog"; import MemoRelationListView from "./MemoRelationListView"; import MemoResourceListView from "./MemoResourceListView"; import showPreviewImageDialog from "./PreviewImageDialog"; import showShareMemoDialog from "./ShareMemoDialog"; import UserAvatar from "./UserAvatar"; import VisibilityIcon from "./VisibilityIcon"; import "@/less/memo.less"; interface Props { memo: Memo; showCreator?: boolean; showVisibility?: boolean; showPinned?: boolean; className?: string; } const MemoView: React.FC = (props: Props) => { const { memo, className } = props; const t = useTranslate(); const navigateTo = useNavigateTo(); const { i18n } = useTranslation(); const memoStore = useMemoStore(); 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 referenceRelations = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE); const readonly = memo.creator !== user?.name; useEffect(() => { (async () => { const user = await userStore.getOrFetchUserByUsername(extractUsernameFromName(memo.creator)); setCreator(user); })(); }, []); // 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); } else { navigateTo(`/m/${memo.id}`); } }; const handleTogglePinMemoBtnClick = async () => { try { if (memo.pinned) { await memoStore.updateMemo( { id: memo.id, pinned: false, }, ["pinned"] ); } else { await memoStore.updateMemo( { id: memo.id, pinned: true, }, ["pinned"] ); } } catch (error) { // do nth } }; const handleEditMemoClick = () => { showMemoEditorDialog({ memoId: memo.id, }); }; const handleMarkMemoClick = () => { showMemoEditorDialog({ relationList: [ { memoId: UNKNOWN_ID, relatedMemoId: memo.id, type: MemoRelation_Type.REFERENCE, }, ], }); }; const handleArchiveMemoClick = async () => { try { await memoStore.updateMemo( { id: memo.id, rowStatus: RowStatus.ARCHIVED, }, ["row_status"] ); } catch (error: any) { console.error(error); toast.error(error.response.data.message); } }; const handleDeleteMemoClick = async () => { showCommonDialog({ title: t("memo.delete-memo"), content: t("memo.delete-confirm"), style: "danger", dialogName: "delete-memo-dialog", onConfirm: async () => { await memoStore.deleteMemo(memo.id); }, }); }; const handleCopyMemoId = () => { copy(String(memo.id)); toast.success("Copied to clipboard!"); }; const handleMemoContentClick = useCallback(async (e: React.MouseEvent) => { const targetEl = e.target as HTMLElement; if (targetEl.tagName === "IMG") { const imgUrl = targetEl.getAttribute("src"); if (imgUrl) { showPreviewImageDialog([imgUrl], 0); } } }, []); return (
{props.showCreator && creator && ( <> {creator.nickname || creator.username} )} {displayTime} {props.showPinned && memo.pinned && ( <> )}
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( <> )}
{!readonly && ( <>
{props.showPinned && ( {memo.pinned ? : } {memo.pinned ? t("common.unpin") : t("common.pin")} )} {t("common.edit")} {t("common.mark")} showShareMemoDialog(memo)}> {t("common.share")} {t("common.archive")} {t("common.delete")}
ID: {memo.id}
)}
); }; export default memo(MemoView);