diff --git a/web/src/components/MemoView/MemoView.tsx b/web/src/components/MemoView/MemoView.tsx index 68ec54f94..33881205c 100644 --- a/web/src/components/MemoView/MemoView.tsx +++ b/web/src/components/MemoView/MemoView.tsx @@ -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) => { +const MemoView: React.FC = (props: MemoViewProps) => { const { memo: memoData, className } = props; const cardRef = useRef(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) => { 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) { diff --git a/web/src/components/MemoView/MemoViewContext.tsx b/web/src/components/MemoView/MemoViewContext.tsx index a9134109d..a0b285785 100644 --- a/web/src/components/MemoView/MemoViewContext.tsx +++ b/web/src/components/MemoView/MemoViewContext.tsx @@ -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(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, }; }; diff --git a/web/src/components/MemoView/components/MemoBody.tsx b/web/src/components/MemoView/components/MemoBody.tsx index dd636f45c..061511e66 100644 --- a/web/src/components/MemoView/components/MemoBody.tsx +++ b/web/src/components/MemoView/components/MemoBody.tsx @@ -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 = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => { +const MemoBody: React.FC = ({ 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); diff --git a/web/src/components/MemoView/components/MemoHeader.tsx b/web/src/components/MemoView/components/MemoHeader.tsx index 85a781c82..be0077e90 100644 --- a/web/src/components/MemoView/components/MemoHeader.tsx +++ b/web/src/components/MemoView/components/MemoHeader.tsx @@ -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 = ({ +const MemoHeader: React.FC = ({ showCreator, showVisibility, showPinned, @@ -39,9 +28,8 @@ const MemoHeader: React.FC = ({ }) => { 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) diff --git a/web/src/components/MemoView/hooks/index.ts b/web/src/components/MemoView/hooks/index.ts index d02778328..e85401901 100644 --- a/web/src/components/MemoView/hooks/index.ts +++ b/web/src/components/MemoView/hooks/index.ts @@ -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"; diff --git a/web/src/components/MemoView/hooks/useMemoActions.ts b/web/src/components/MemoView/hooks/useMemoActions.ts index 223794de4..c2fb94a53 100644 --- a/web/src/components/MemoView/hooks/useMemoActions.ts +++ b/web/src/components/MemoView/hooks/useMemoActions.ts @@ -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; diff --git a/web/src/components/MemoView/hooks/useMemoCreator.ts b/web/src/components/MemoView/hooks/useMemoCreator.ts deleted file mode 100644 index 6a64b31a1..000000000 --- a/web/src/components/MemoView/hooks/useMemoCreator.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useUser } from "@/hooks/useUserQueries"; - -export const useMemoCreator = (creatorName: string) => { - const { data: creator } = useUser(creatorName); - return creator; -}; diff --git a/web/src/components/MemoView/hooks/useMemoEditor.ts b/web/src/components/MemoView/hooks/useMemoEditor.ts deleted file mode 100644 index 98f8becf4..000000000 --- a/web/src/components/MemoView/hooks/useMemoEditor.ts +++ /dev/null @@ -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), - }; -}; diff --git a/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts b/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts index 22b50a5f5..e5f77a1de 100644 --- a/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts +++ b/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts @@ -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 }; };