diff --git a/web/src/components/MemoView/MemoView.tsx b/web/src/components/MemoView/MemoView.tsx index bbfbc2604..14addba0e 100644 --- a/web/src/components/MemoView/MemoView.tsx +++ b/web/src/components/MemoView/MemoView.tsx @@ -1,6 +1,7 @@ import { observer } from "mobx-react-lite"; import { memo, useMemo, useRef, useState } from "react"; import { cn } from "@/lib/utils"; +import type { Memo } from "@/types/proto/api/v1/memo_service"; import MemoEditor from "../MemoEditor"; import PreviewImageDialog from "../PreviewImageDialog"; import { MemoBody, MemoHeader } from "./components"; @@ -16,40 +17,31 @@ import { useNsfwContent, } from "./hooks"; import { MemoViewContext } from "./MemoViewContext"; -import type { MemoViewProps } from "./types"; -/** - * MemoView component displays a single memo card with full functionality including: - * - Creator information and display time - * - Memo content with markdown rendering - * - Attachments and location - * - Reactions and comments - * - Edit mode with inline editor - * - Keyboard shortcuts for quick actions - * - NSFW content blur protection - */ -const MemoView: React.FC = observer((props: MemoViewProps) => { +interface Props { + memo: Memo; + compact?: boolean; + showCreator?: boolean; + showVisibility?: boolean; + showPinned?: boolean; + showNsfwContent?: boolean; + className?: string; + parentPage?: string; +} + +const MemoView: React.FC = observer((props: Props) => { const { memo: memoData, className } = props; const cardRef = useRef(null); - - // State const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false); - // Custom hooks for data fetching const creator = useMemoCreator(memoData.creator); - - // Custom hooks for derived state - const { commentAmount, relativeTimeFormat, isArchived, readonly, isInMemoDetailPage, parentPage } = useMemoViewDerivedState({ - memo: memoData, - parentPage: props.parentPage, - }); - - // Custom hooks for UI state management + const { commentAmount, relativeTimeFormat, isArchived, readonly, isInMemoDetailPage, parentPage } = useMemoViewDerivedState( + memoData, + props.parentPage, + ); const { nsfw, showNSFWContent, toggleNsfwVisibility } = useNsfwContent(memoData, props.showNsfwContent); const { previewState, openPreview, setPreviewOpen } = useImagePreview(); const { showEditor, openEditor, handleEditorConfirm, handleEditorCancel } = useMemoEditor(); - - // Custom hooks for actions const { archiveMemo, unpinMemo } = useMemoActions(memoData); const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({ memoName: memoData.name, @@ -58,9 +50,7 @@ const MemoView: React.FC = observer((props: MemoViewProps) => { openEditor, openPreview, }); - - // Keyboard shortcuts - const { handleShortcutActivation } = useKeyboardShortcuts(cardRef, { + useKeyboardShortcuts(cardRef, { enabled: true, readonly, showEditor, @@ -69,8 +59,6 @@ const MemoView: React.FC = observer((props: MemoViewProps) => { onArchive: archiveMemo, }); - // Memoize context value to prevent unnecessary re-renders - // IMPORTANT: This must be before the early return to satisfy Rules of Hooks const contextValue = useMemo( () => ({ memo: memoData, @@ -87,7 +75,6 @@ const MemoView: React.FC = observer((props: MemoViewProps) => { [memoData, creator, isArchived, readonly, isInMemoDetailPage, parentPage, commentAmount, relativeTimeFormat, nsfw, showNSFWContent], ); - // Render inline editor when editing if (showEditor) { return ( = observer((props: MemoViewProps) => { ); } - // Render memo card return ( -
handleShortcutActivation(true)} - onBlur={() => handleShortcutActivation(false)} - > +
(null); -/** - * Hook to access MemoView context - * @throws Error if used outside of MemoViewContext.Provider - */ export const useMemoViewContext = (): MemoViewContextValue => { const context = useContext(MemoViewContext); if (!context) { diff --git a/web/src/components/MemoView/components/MemoBody.tsx b/web/src/components/MemoView/components/MemoBody.tsx index 0b077a5fb..5841998bc 100644 --- a/web/src/components/MemoView/components/MemoBody.tsx +++ b/web/src/components/MemoView/components/MemoBody.tsx @@ -5,18 +5,15 @@ import MemoContent from "../../MemoContent"; import { MemoReactionListView } from "../../MemoReactionListView"; import { AttachmentList, LocationDisplay, RelationList } from "../../memo-metadata"; import { useMemoViewContext } from "../MemoViewContext"; -import type { MemoBodyProps } from "../types"; -/** - * MemoBody component displays the main content of a memo including: - * - Memo content (markdown) - * - Location display - * - Attachments - * - Related memos - * - Reactions - * - NSFW content overlay - */ -const MemoBody: React.FC = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => { +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 t = useTranslate(); // Get shared state from context diff --git a/web/src/components/MemoView/components/MemoHeader.tsx b/web/src/components/MemoView/components/MemoHeader.tsx index ceaa9fa99..cc2c5fa0d 100644 --- a/web/src/components/MemoView/components/MemoHeader.tsx +++ b/web/src/components/MemoView/components/MemoHeader.tsx @@ -12,20 +12,20 @@ import { ReactionSelector } from "../../reactions"; import UserAvatar from "../../UserAvatar"; import VisibilityIcon from "../../VisibilityIcon"; import { useMemoViewContext } from "../MemoViewContext"; -import type { MemoHeaderProps } from "../types"; -/** - * MemoHeader component displays the top section of a memo card including: - * - Creator info (avatar, name) when showCreator is true - * - Display time (relative or absolute) - * - Reaction selector - * - Comment count link - * - Visibility icon - * - Pin indicator - * - NSFW hide button - * - Action menu - */ -const MemoHeader: React.FC = ({ +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 = ({ showCreator, showVisibility, showPinned, @@ -130,9 +130,6 @@ const MemoHeader: React.FC = ({ ); }; -/** - * Creator display with avatar and name - */ interface CreatorDisplayProps { creator: User; displayTime: React.ReactNode; @@ -163,9 +160,6 @@ const CreatorDisplay: React.FC = ({ creator, displayTime, o ); -/** - * Simple time display without creator info - */ interface TimeDisplayProps { displayTime: React.ReactNode; onGotoDetail: () => void; diff --git a/web/src/components/MemoView/constants.ts b/web/src/components/MemoView/constants.ts index 19f3a631d..485430096 100644 --- a/web/src/components/MemoView/constants.ts +++ b/web/src/components/MemoView/constants.ts @@ -1,19 +1,8 @@ -/** - * Constants for MemoView component - */ - -/** CSS class for memo card styling */ export const MEMO_CARD_BASE_CLASSES = "relative group flex flex-col justify-start items-start bg-card w-full px-4 py-3 mb-2 gap-2 text-card-foreground rounded-lg border border-border transition-colors"; -/** Keyboard shortcut keys */ -export const KEYBOARD_SHORTCUTS = { - EDIT: "e", - ARCHIVE: "a", -} as const; +export const KEYBOARD_SHORTCUTS = { EDIT: "e", ARCHIVE: "a" } as const; -/** Text input element types for keyboard shortcut filtering */ export const TEXT_INPUT_TYPES = ["text", "search", "email", "password", "url", "tel", "number"] as const; -/** Time threshold for relative time format (24 hours in milliseconds) */ export const RELATIVE_TIME_THRESHOLD_MS = 1000 * 60 * 60 * 24; diff --git a/web/src/components/MemoView/hooks/index.ts b/web/src/components/MemoView/hooks/index.ts index e324132f4..89c1fa129 100644 --- a/web/src/components/MemoView/hooks/index.ts +++ b/web/src/components/MemoView/hooks/index.ts @@ -1,7 +1,4 @@ -export type { UseMemoEditorReturn } from "./useMemoEditor"; export { useMemoEditor } from "./useMemoEditor"; -export type { UseMemoHandlersOptions, UseMemoHandlersReturn } from "./useMemoHandlers"; export { useMemoHandlers } from "./useMemoHandlers"; -export type { UseMemoViewDerivedStateOptions, UseMemoViewDerivedStateReturn } from "./useMemoViewDerivedState"; export { useMemoViewDerivedState } from "./useMemoViewDerivedState"; export { useImagePreview, useKeyboardShortcuts, useMemoActions, useMemoCreator, useNsfwContent } from "./useMemoViewState"; diff --git a/web/src/components/MemoView/hooks/useMemoEditor.ts b/web/src/components/MemoView/hooks/useMemoEditor.ts index 5737db0a6..480498938 100644 --- a/web/src/components/MemoView/hooks/useMemoEditor.ts +++ b/web/src/components/MemoView/hooks/useMemoEditor.ts @@ -1,37 +1,16 @@ -import { useCallback, useState } from "react"; +import { useState } from "react"; import { userStore } from "@/store"; -export interface UseMemoEditorReturn { - showEditor: boolean; - openEditor: () => void; - handleEditorConfirm: () => void; - handleEditorCancel: () => void; -} - -/** - * Hook for managing memo editor state and actions - * Encapsulates all editor-related state and handlers - */ -export const useMemoEditor = (): UseMemoEditorReturn => { +export const useMemoEditor = () => { const [showEditor, setShowEditor] = useState(false); - const openEditor = useCallback(() => { - setShowEditor(true); - }, []); - - const handleEditorConfirm = useCallback(() => { - setShowEditor(false); - userStore.setStatsStateId(); - }, []); - - const handleEditorCancel = useCallback(() => { - setShowEditor(false); - }, []); - return { showEditor, - openEditor, - handleEditorConfirm, - handleEditorCancel, + openEditor: () => setShowEditor(true), + handleEditorConfirm: () => { + setShowEditor(false); + userStore.setStatsStateId(); + }, + handleEditorCancel: () => setShowEditor(false), }; }; diff --git a/web/src/components/MemoView/hooks/useMemoHandlers.ts b/web/src/components/MemoView/hooks/useMemoHandlers.ts index 8ce8dd79c..e5dcee1e6 100644 --- a/web/src/components/MemoView/hooks/useMemoHandlers.ts +++ b/web/src/components/MemoView/hooks/useMemoHandlers.ts @@ -1,52 +1,32 @@ import { useCallback } from "react"; import useNavigateTo from "@/hooks/useNavigateTo"; import { instanceStore } from "@/store"; -import type { UseImagePreviewReturn } from "../types"; -export interface UseMemoHandlersOptions { +interface UseMemoHandlersOptions { memoName: string; parentPage: string; readonly: boolean; openEditor: () => void; - openPreview: UseImagePreviewReturn["openPreview"]; + openPreview: (url: string) => void; } -export interface UseMemoHandlersReturn { - handleGotoMemoDetailPage: () => void; - handleMemoContentClick: (e: React.MouseEvent) => void; - handleMemoContentDoubleClick: (e: React.MouseEvent) => void; -} - -/** - * Hook for managing memo event handlers - * Centralizes all click and interaction handlers - */ -export const useMemoHandlers = (options: UseMemoHandlersOptions): UseMemoHandlersReturn => { +export const useMemoHandlers = (options: UseMemoHandlersOptions) => { const { memoName, parentPage, readonly, openEditor, openPreview } = options; const navigateTo = useNavigateTo(); + // These useCallbacks are necessary since they have real dependencies const handleGotoMemoDetailPage = useCallback(() => { - navigateTo(`/${memoName}`, { - state: { from: parentPage }, - }); + navigateTo(`/${memoName}`, { state: { from: parentPage } }); }, [memoName, parentPage, navigateTo]); const handleMemoContentClick = useCallback( (e: React.MouseEvent) => { const targetEl = e.target as HTMLElement; - if (targetEl.tagName === "IMG") { - // Check if the image is inside a link const linkElement = targetEl.closest("a"); - if (linkElement) { - // If image is inside a link, only navigate to the link (don't show preview) - return; - } - + if (linkElement) return; // If image is inside a link, don't show preview const imgUrl = targetEl.getAttribute("src"); - if (imgUrl) { - openPreview(imgUrl); - } + if (imgUrl) openPreview(imgUrl); } }, [openPreview], @@ -55,9 +35,7 @@ export const useMemoHandlers = (options: UseMemoHandlersOptions): UseMemoHandler const handleMemoContentDoubleClick = useCallback( (e: React.MouseEvent) => { if (readonly) return; - - const instanceMemoRelatedSetting = instanceStore.state.memoRelatedSetting; - if (instanceMemoRelatedSetting.enableDoubleClickEdit) { + if (instanceStore.state.memoRelatedSetting.enableDoubleClickEdit) { e.preventDefault(); openEditor(); } @@ -65,9 +43,5 @@ export const useMemoHandlers = (options: UseMemoHandlersOptions): UseMemoHandler [readonly, openEditor], ); - return { - handleGotoMemoDetailPage, - handleMemoContentClick, - handleMemoContentDoubleClick, - }; + return { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick }; }; diff --git a/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts b/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts index 97e947e1e..439804cf4 100644 --- a/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts +++ b/web/src/components/MemoView/hooks/useMemoViewDerivedState.ts @@ -1,4 +1,3 @@ -import { useMemo } from "react"; import { useLocation } from "react-router-dom"; import useCurrentUser from "@/hooks/useCurrentUser"; import { State } from "@/types/proto/api/v1/common"; @@ -7,55 +6,21 @@ import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service"; import { isSuperUser } from "@/utils/user"; import { RELATIVE_TIME_THRESHOLD_MS } from "../constants"; -export interface UseMemoViewDerivedStateOptions { - memo: Memo; - parentPage?: string; -} - -export interface UseMemoViewDerivedStateReturn { - commentAmount: number; - relativeTimeFormat: "datetime" | "auto"; - isArchived: boolean; - readonly: boolean; - isInMemoDetailPage: boolean; - parentPage: string; -} - -/** - * Hook for computing derived state from memo data - * Centralizes all computed values to avoid repetition and improve readability - */ -export const useMemoViewDerivedState = (options: UseMemoViewDerivedStateOptions): UseMemoViewDerivedStateReturn => { - const { memo, parentPage: parentPageProp } = options; +export const useMemoViewDerivedState = (memo: Memo, parentPageProp?: string) => { const location = useLocation(); const user = useCurrentUser(); - // Compute all derived state - const commentAmount = useMemo( - () => - memo.relations.filter((relation) => relation.type === MemoRelation_Type.COMMENT && relation.relatedMemo?.name === memo.name).length, - [memo.relations, memo.name], - ); + const commentAmount = memo.relations.filter( + (relation) => relation.type === MemoRelation_Type.COMMENT && relation.relatedMemo?.name === memo.name, + ).length; - const relativeTimeFormat: "datetime" | "auto" = useMemo( - () => (memo.displayTime && Date.now() - memo.displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto"), - [memo.displayTime], - ); + const relativeTimeFormat: "datetime" | "auto" = + memo.displayTime && Date.now() - memo.displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto"; - const isArchived = useMemo(() => memo.state === State.ARCHIVED, [memo.state]); + 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; - const readonly = useMemo(() => memo.creator !== user?.name && !isSuperUser(user), [memo.creator, user]); - - const isInMemoDetailPage = useMemo(() => location.pathname.startsWith(`/${memo.name}`), [location.pathname, memo.name]); - - const parentPage = useMemo(() => parentPageProp || location.pathname, [parentPageProp, location.pathname]); - - return { - commentAmount, - relativeTimeFormat, - isArchived, - readonly, - isInMemoDetailPage, - parentPage, - }; + return { commentAmount, relativeTimeFormat, isArchived, readonly, isInMemoDetailPage, parentPage }; }; diff --git a/web/src/components/MemoView/hooks/useMemoViewState.ts b/web/src/components/MemoView/hooks/useMemoViewState.ts index ef0a05d60..7640c170f 100644 --- a/web/src/components/MemoView/hooks/useMemoViewState.ts +++ b/web/src/components/MemoView/hooks/useMemoViewState.ts @@ -1,38 +1,34 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import toast from "react-hot-toast"; import { instanceStore, memoStore, userStore } from "@/store"; import { State } from "@/types/proto/api/v1/common"; import type { Memo } from "@/types/proto/api/v1/memo_service"; import { useTranslate } from "@/utils/i18n"; import { KEYBOARD_SHORTCUTS, TEXT_INPUT_TYPES } from "../constants"; -import type { - ImagePreviewState, - UseImagePreviewReturn, - UseKeyboardShortcutsOptions, - UseMemoActionsReturn, - UseNsfwContentReturn, -} from "../types"; -/** - * Hook for handling memo actions (archive, unpin) - */ -export const useMemoActions = (memo: Memo): UseMemoActionsReturn => { +interface ImagePreviewState { + open: boolean; + urls: string[]; + index: number; +} + +interface UseKeyboardShortcutsOptions { + enabled: boolean; + readonly: boolean; + showEditor: boolean; + isArchived: boolean; + onEdit: () => void; + onArchive: () => Promise; +} + +export const useMemoActions = (memo: Memo) => { const t = useTranslate(); const isArchived = memo.state === State.ARCHIVED; - const archiveMemo = useCallback(async () => { - if (isArchived) { - return; - } - + const archiveMemo = async () => { + if (isArchived) return; try { - await memoStore.updateMemo( - { - name: memo.name, - state: State.ARCHIVED, - }, - ["state"], - ); + await memoStore.updateMemo({ name: memo.name, state: State.ARCHIVED }, ["state"]); toast.success(t("message.archived-successfully")); userStore.setStatsStateId(); } catch (error: unknown) { @@ -40,66 +36,37 @@ export const useMemoActions = (memo: Memo): UseMemoActionsReturn => { const err = error as { details?: string }; toast.error(err?.details || "Failed to archive memo"); } - }, [isArchived, memo.name, t]); + }; - const unpinMemo = useCallback(async () => { - if (!memo.pinned) { - return; - } - - await memoStore.updateMemo( - { - name: memo.name, - pinned: false, - }, - ["pinned"], - ); - }, [memo.name, memo.pinned]); + const unpinMemo = async () => { + if (!memo.pinned) return; + await memoStore.updateMemo({ name: memo.name, pinned: false }, ["pinned"]); + }; return { archiveMemo, unpinMemo }; }; -/** - * Hook for handling keyboard shortcuts on the memo card - */ -export const useKeyboardShortcuts = ( - cardRef: React.RefObject, - options: UseKeyboardShortcutsOptions, -): { - shortcutActive: boolean; - handleShortcutActivation: (active: boolean) => void; -} => { +const isTextInputElement = (element: HTMLElement | null): boolean => { + if (!element) return false; + if (element.isContentEditable) return true; + if (element instanceof HTMLTextAreaElement) return true; + if (element instanceof HTMLInputElement) { + return TEXT_INPUT_TYPES.includes(element.type as (typeof TEXT_INPUT_TYPES)[number]); + } + return false; +}; + +export const useKeyboardShortcuts = (cardRef: React.RefObject, options: UseKeyboardShortcutsOptions) => { const { enabled, readonly, showEditor, isArchived, onEdit, onArchive } = options; - const [shortcutActive, setShortcutActive] = useState(false); - - const isTextInputElement = useCallback((element: HTMLElement | null): boolean => { - if (!element) return false; - if (element.isContentEditable) return true; - if (element instanceof HTMLTextAreaElement) return true; - - if (element instanceof HTMLInputElement) { - return TEXT_INPUT_TYPES.includes(element.type as (typeof TEXT_INPUT_TYPES)[number]); - } - - return false; - }, []); useEffect(() => { - if (!enabled || readonly || showEditor || !cardRef.current) { - return; - } + if (!enabled || readonly || showEditor || !cardRef.current) return; const cardEl = cardRef.current; - const handleKeyDown = (event: KeyboardEvent) => { const target = event.target as HTMLElement | null; - if (!cardEl.contains(target) || isTextInputElement(target)) { - return; - } - - if (event.metaKey || event.ctrlKey || event.altKey) { - return; - } + if (!cardEl.contains(target) || isTextInputElement(target)) return; + if (event.metaKey || event.ctrlKey || event.altKey) return; const key = event.key.toLowerCase(); if (key === KEYBOARD_SHORTCUTS.EDIT) { @@ -113,29 +80,10 @@ export const useKeyboardShortcuts = ( cardEl.addEventListener("keydown", handleKeyDown); return () => cardEl.removeEventListener("keydown", handleKeyDown); - }, [enabled, readonly, showEditor, isArchived, onEdit, onArchive, cardRef, isTextInputElement]); - - useEffect(() => { - if (showEditor || readonly) { - setShortcutActive(false); - } - }, [showEditor, readonly]); - - const handleShortcutActivation = useCallback( - (active: boolean) => { - if (readonly) return; - setShortcutActive(active); - }, - [readonly], - ); - - return { shortcutActive, handleShortcutActivation }; + }, [enabled, readonly, showEditor, isArchived, onEdit, onArchive, cardRef]); }; -/** - * Hook for managing NSFW content visibility - */ -export const useNsfwContent = (memo: Memo, initialShowNsfw?: boolean): UseNsfwContentReturn => { +export const useNsfwContent = (memo: Memo, initialShowNsfw?: boolean) => { const [showNSFWContent, setShowNSFWContent] = useState(initialShowNsfw ?? false); const instanceMemoRelatedSetting = instanceStore.state.memoRelatedSetting; @@ -143,50 +91,23 @@ export const useNsfwContent = (memo: Memo, initialShowNsfw?: boolean): UseNsfwCo instanceMemoRelatedSetting.enableBlurNsfwContent && memo.tags?.some((tag) => instanceMemoRelatedSetting.nsfwTags.some((nsfwTag) => tag === nsfwTag || tag.startsWith(`${nsfwTag}/`))); - const toggleNsfwVisibility = useCallback(() => { - setShowNSFWContent((prev) => !prev); - }, []); - return { nsfw: nsfw ?? false, showNSFWContent, - toggleNsfwVisibility, + toggleNsfwVisibility: () => setShowNSFWContent((prev) => !prev), }; }; -/** - * Hook for managing image preview dialog state - */ -export const useImagePreview = (): UseImagePreviewReturn => { - const [previewState, setPreviewState] = useState({ - open: false, - urls: [], - index: 0, - }); - - const openPreview = useCallback((url: string) => { - setPreviewState({ open: true, urls: [url], index: 0 }); - }, []); - - const closePreview = useCallback(() => { - setPreviewState((prev) => ({ ...prev, open: false })); - }, []); - - const setPreviewOpen = useCallback((open: boolean) => { - setPreviewState((prev) => ({ ...prev, open })); - }, []); +export const useImagePreview = () => { + const [previewState, setPreviewState] = useState({ open: false, urls: [], index: 0 }); return { previewState, - openPreview, - closePreview, - setPreviewOpen, + openPreview: (url: string) => setPreviewState({ open: true, urls: [url], index: 0 }), + setPreviewOpen: (open: boolean) => setPreviewState((prev) => ({ ...prev, open })), }; }; -/** - * Hook for fetching and managing memo creator data - */ export const useMemoCreator = (creatorName: string) => { const [creator, setCreator] = useState(userStore.getUserByName(creatorName)); const fetchedRef = useRef(false); @@ -194,7 +115,6 @@ export const useMemoCreator = (creatorName: string) => { useEffect(() => { if (fetchedRef.current) return; fetchedRef.current = true; - (async () => { const user = await userStore.getOrFetchUserByName(creatorName); setCreator(user); diff --git a/web/src/components/MemoView/index.ts b/web/src/components/MemoView/index.ts index 6d7dc8109..b6e6a0bf5 100644 --- a/web/src/components/MemoView/index.ts +++ b/web/src/components/MemoView/index.ts @@ -10,33 +10,4 @@ export { MemoBody, MemoHeader } from "./components"; export * from "./constants"; -export type { - UseMemoEditorReturn, - UseMemoHandlersOptions, - UseMemoHandlersReturn, - UseMemoViewDerivedStateOptions, - UseMemoViewDerivedStateReturn, -} from "./hooks"; -export { - useImagePreview, - useKeyboardShortcuts, - useMemoActions, - useMemoCreator, - useMemoEditor, - useMemoHandlers, - useMemoViewDerivedState, - useNsfwContent, -} from "./hooks"; export { default, default as MemoView } from "./MemoView"; -export type { MemoViewContextValue } from "./MemoViewContext"; -export { MemoViewContext, useMemoViewContext } from "./MemoViewContext"; -export type { - ImagePreviewState, - MemoBodyProps, - MemoHeaderProps, - MemoViewProps, - UseImagePreviewReturn, - UseKeyboardShortcutsOptions, - UseMemoActionsReturn, - UseNsfwContentReturn, -} from "./types"; diff --git a/web/src/components/MemoView/types.ts b/web/src/components/MemoView/types.ts deleted file mode 100644 index 9ab19a329..000000000 --- a/web/src/components/MemoView/types.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { Memo } from "@/types/proto/api/v1/memo_service"; -import type { User } from "@/types/proto/api/v1/user_service"; - -/** - * Props for the MemoView component - */ -export interface MemoViewProps { - /** The memo data to display */ - memo: Memo; - /** Enable compact mode with truncated content */ - compact?: boolean; - /** Show creator avatar and name */ - showCreator?: boolean; - /** Show visibility icon */ - showVisibility?: boolean; - /** Show pinned indicator */ - showPinned?: boolean; - /** Show NSFW content without blur */ - showNsfwContent?: boolean; - /** Additional CSS classes */ - className?: string; - /** Parent page path for navigation state */ - parentPage?: string; -} - -/** - * Props for the MemoHeader component - * Note: Most data props now come from MemoViewContext - */ -export interface MemoHeaderProps { - // Display options - showCreator?: boolean; - showVisibility?: boolean; - showPinned?: boolean; - // Callbacks - onEdit: () => void; - onGotoDetail: () => void; - onUnpin: () => void; - onToggleNsfwVisibility?: () => void; - // Reaction state - reactionSelectorOpen: boolean; - onReactionSelectorOpenChange: (open: boolean) => void; -} - -/** - * Props for the MemoBody component - * Note: Most data props now come from MemoViewContext - */ -export interface MemoBodyProps { - // Display options - compact?: boolean; - // Callbacks - onContentClick: (e: React.MouseEvent) => void; - onContentDoubleClick: (e: React.MouseEvent) => void; - onToggleNsfwVisibility: () => void; -} - -/** - * State for image preview dialog - */ -export interface ImagePreviewState { - open: boolean; - urls: string[]; - index: number; -} - -/** - * Return type for useMemoActions hook - */ -export interface UseMemoActionsReturn { - archiveMemo: () => Promise; - unpinMemo: () => Promise; -} - -/** - * Return type for useKeyboardShortcuts hook - */ -export interface UseKeyboardShortcutsOptions { - enabled: boolean; - readonly: boolean; - showEditor: boolean; - isArchived: boolean; - onEdit: () => void; - onArchive: () => Promise; -} - -/** - * Return type for useNsfwContent hook - */ -export interface UseNsfwContentReturn { - nsfw: boolean; - showNSFWContent: boolean; - toggleNsfwVisibility: () => void; -} - -/** - * Return type for useImagePreview hook - */ -export interface UseImagePreviewReturn { - previewState: ImagePreviewState; - openPreview: (url: string) => void; - closePreview: () => void; - setPreviewOpen: (open: boolean) => void; -}