refactor: streamline MemoView component and related hooks, removing unused code and integrating user data

This commit is contained in:
Johnny 2025-12-28 12:59:55 +08:00
parent 64ae13839a
commit 40585607f4
9 changed files with 26 additions and 99 deletions

View File

@ -1,32 +1,13 @@
import { memo, useMemo, useRef, useState } from "react";
import { useUser } from "@/hooks/useUserQueries";
import { cn } from "@/lib/utils";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
import MemoEditor from "../MemoEditor";
import PreviewImageDialog from "../PreviewImageDialog";
import { MemoBody, MemoHeader } from "./components";
import { MEMO_CARD_BASE_CLASSES } from "./constants";
import {
useImagePreview,
useKeyboardShortcuts,
useMemoActions,
useMemoCreator,
useMemoEditor,
useMemoHandlers,
useMemoViewDerivedState,
useNsfwContent,
} from "./hooks";
import { useImagePreview, useKeyboardShortcuts, useMemoActions, useMemoHandlers, useMemoViewDerivedState, useNsfwContent } from "./hooks";
import { MemoViewContext } from "./MemoViewContext";
interface Props {
memo: Memo;
compact?: boolean;
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
showNsfwContent?: boolean;
className?: string;
parentPage?: string;
}
import type { MemoViewProps } from "./types";
/**
* MemoView component displays a memo card with all its content, metadata, and interactive elements.
@ -49,17 +30,22 @@ interface Props {
* />
* ```
*/
const MemoView: React.FC<Props> = (props: Props) => {
const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const { memo: memoData, className } = props;
const cardRef = useRef<HTMLDivElement>(null);
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
const [showEditor, setShowEditor] = useState(false);
const creator = useMemoCreator(memoData.creator);
const creator = useUser(memoData.creator).data;
const { isArchived, readonly, parentPage } = useMemoViewDerivedState(memoData, props.parentPage);
const { showNSFWContent, toggleNsfwVisibility } = useNsfwContent(memoData, props.showNsfwContent);
const { nsfw, showNSFWContent, toggleNsfwVisibility } = useNsfwContent(memoData, props.showNsfwContent);
const { previewState, openPreview, setPreviewOpen } = useImagePreview();
const { showEditor, openEditor, handleEditorConfirm, handleEditorCancel } = useMemoEditor();
const { archiveMemo, unpinMemo } = useMemoActions(memoData);
const { archiveMemo, unpinMemo } = useMemoActions(memoData, isArchived);
const handleEditorConfirm = () => setShowEditor(false);
const handleEditorCancel = () => setShowEditor(false);
const openEditor = () => setShowEditor(true);
const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({
memoName: memoData.name,
parentPage,
@ -76,15 +62,15 @@ const MemoView: React.FC<Props> = (props: Props) => {
onArchive: archiveMemo,
});
// Minimal essential context - only non-derivable data
const contextValue = useMemo(
() => ({
memo: memoData,
creator,
parentPage,
showNSFWContent,
nsfw,
}),
[memoData, creator, parentPage, showNSFWContent],
[memoData, creator, parentPage, showNSFWContent, nsfw],
);
if (showEditor) {

View File

@ -9,12 +9,12 @@ import type { User } from "@/types/proto/api/v1/user_service_pb";
import { isSuperUser } from "@/utils/user";
import { RELATIVE_TIME_THRESHOLD_MS } from "./constants";
// Minimal essential context - only data that cannot be easily derived
export interface MemoViewContextValue {
memo: Memo;
creator: User | undefined;
parentPage: string;
showNSFWContent: boolean;
nsfw: boolean;
}
export const MemoViewContext = createContext<MemoViewContextValue | null>(null);
@ -27,7 +27,6 @@ export const useMemoViewContext = (): MemoViewContextValue => {
return context;
};
// Utility hooks to derive common values from context
export const useMemoViewDerived = () => {
const { memo } = useMemoViewContext();
const location = useLocation();
@ -45,14 +44,11 @@ export const useMemoViewDerived = () => {
const relativeTimeFormat: "datetime" | "auto" =
displayTime && Date.now() - displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto";
const nsfw = memo.tags.some((tag) => tag.toLowerCase() === "nsfw");
return {
isArchived,
readonly,
isInMemoDetailPage,
commentAmount,
relativeTimeFormat,
nsfw,
};
};

View File

@ -4,21 +4,13 @@ import { useTranslate } from "@/utils/i18n";
import MemoContent from "../../MemoContent";
import { MemoReactionListView } from "../../MemoReactionListView";
import { AttachmentList, LocationDisplay, RelationList } from "../../memo-metadata";
import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
import { useMemoViewContext } from "../MemoViewContext";
import type { MemoBodyProps } from "../types";
interface Props {
compact?: boolean;
onContentClick: (e: React.MouseEvent) => void;
onContentDoubleClick: (e: React.MouseEvent) => void;
onToggleNsfwVisibility: () => void;
}
const MemoBody: React.FC<Props> = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => {
const MemoBody: React.FC<MemoBodyProps> = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => {
const t = useTranslate();
// Get essential context and derive other values
const { memo, parentPage, showNSFWContent } = useMemoViewContext();
const { nsfw } = useMemoViewDerived();
const { memo, parentPage, showNSFWContent, nsfw } = useMemoViewContext();
const referencedMemos = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE);

View File

@ -13,20 +13,9 @@ import { ReactionSelector } from "../../MemoReactionListView";
import UserAvatar from "../../UserAvatar";
import VisibilityIcon from "../../VisibilityIcon";
import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
import type { MemoHeaderProps } from "../types";
interface Props {
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
onEdit: () => void;
onGotoDetail: () => void;
onUnpin: () => void;
onToggleNsfwVisibility?: () => void;
reactionSelectorOpen: boolean;
onReactionSelectorOpenChange: (open: boolean) => void;
}
const MemoHeader: React.FC<Props> = ({
const MemoHeader: React.FC<MemoHeaderProps> = ({
showCreator,
showVisibility,
showPinned,
@ -39,9 +28,8 @@ const MemoHeader: React.FC<Props> = ({
}) => {
const t = useTranslate();
// Get essential context and derive other values
const { memo, creator, parentPage, showNSFWContent } = useMemoViewContext();
const { isArchived, readonly, isInMemoDetailPage, commentAmount, relativeTimeFormat, nsfw } = useMemoViewDerived();
const { memo, creator, parentPage, showNSFWContent, nsfw } = useMemoViewContext();
const { isArchived, readonly, isInMemoDetailPage, commentAmount, relativeTimeFormat } = useMemoViewDerived();
const displayTime = isArchived ? (
(memo.displayTime ? timestampDate(memo.displayTime) : undefined)?.toLocaleString(i18n.language)

View File

@ -1,8 +1,6 @@
export { useImagePreview } from "./useImagePreview";
export { useKeyboardShortcuts } from "./useKeyboardShortcuts";
export { useMemoActions } from "./useMemoActions";
export { useMemoCreator } from "./useMemoCreator";
export { useMemoEditor } from "./useMemoEditor";
export { useMemoHandlers } from "./useMemoHandlers";
export { useMemoViewDerivedState } from "./useMemoViewDerivedState";
export { useNsfwContent } from "./useNsfwContent";

View File

@ -5,10 +5,9 @@ import { State } from "@/types/proto/api/v1/common_pb";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
import { useTranslate } from "@/utils/i18n";
export const useMemoActions = (memo: Memo) => {
export const useMemoActions = (memo: Memo, isArchived: boolean) => {
const t = useTranslate();
const { mutateAsync: updateMemo } = useUpdateMemo();
const isArchived = memo.state === State.ARCHIVED;
const archiveMemo = async () => {
if (isArchived) return;

View File

@ -1,6 +0,0 @@
import { useUser } from "@/hooks/useUserQueries";
export const useMemoCreator = (creatorName: string) => {
const { data: creator } = useUser(creatorName);
return creator;
};

View File

@ -1,14 +0,0 @@
import { useState } from "react";
export const useMemoEditor = () => {
const [showEditor, setShowEditor] = useState(false);
return {
showEditor,
openEditor: () => setShowEditor(true),
handleEditorConfirm: () => {
setShowEditor(false);
},
handleEditorCancel: () => setShowEditor(false),
};
};

View File

@ -1,28 +1,16 @@
import { timestampDate } from "@bufbuild/protobuf/wkt";
import { useLocation } from "react-router-dom";
import useCurrentUser from "@/hooks/useCurrentUser";
import { State } from "@/types/proto/api/v1/common_pb";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb";
import { isSuperUser } from "@/utils/user";
import { RELATIVE_TIME_THRESHOLD_MS } from "../constants";
export const useMemoViewDerivedState = (memo: Memo, parentPageProp?: string) => {
const location = useLocation();
const user = useCurrentUser();
const commentAmount = memo.relations.filter(
(relation) => relation.type === MemoRelation_Type.COMMENT && relation.relatedMemo?.name === memo.name,
).length;
const displayTime = memo.displayTime ? timestampDate(memo.displayTime) : undefined;
const relativeTimeFormat: "datetime" | "auto" =
displayTime && Date.now() - displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto";
const isArchived = memo.state === State.ARCHIVED;
const readonly = memo.creator !== user?.name && !isSuperUser(user);
const isInMemoDetailPage = location.pathname.startsWith(`/${memo.name}`);
const parentPage = parentPageProp || location.pathname;
return { commentAmount, relativeTimeFormat, isArchived, readonly, isInMemoDetailPage, parentPage };
return { isArchived, readonly, parentPage };
};