memos/web/src/components/MemoView/MemoView.tsx

125 lines
3.7 KiB
TypeScript

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_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 { MemoViewContext } from "./MemoViewContext";
interface Props {
memo: Memo;
compact?: boolean;
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
showNsfwContent?: boolean;
className?: string;
parentPage?: string;
}
const MemoView: React.FC<Props> = observer((props: Props) => {
const { memo: memoData, className } = props;
const cardRef = useRef<HTMLDivElement>(null);
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
const creator = useMemoCreator(memoData.creator);
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();
const { archiveMemo, unpinMemo } = useMemoActions(memoData);
const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({
memoName: memoData.name,
parentPage,
readonly,
openEditor,
openPreview,
});
useKeyboardShortcuts(cardRef, {
enabled: true,
readonly,
showEditor,
isArchived,
onEdit: openEditor,
onArchive: archiveMemo,
});
const contextValue = useMemo(
() => ({
memo: memoData,
creator,
isArchived,
readonly,
isInMemoDetailPage,
parentPage,
commentAmount,
relativeTimeFormat,
nsfw,
showNSFWContent,
}),
[memoData, creator, isArchived, readonly, isInMemoDetailPage, parentPage, commentAmount, relativeTimeFormat, nsfw, showNSFWContent],
);
if (showEditor) {
return (
<MemoEditor
autoFocus
className="mb-2"
cacheKey={`inline-memo-editor-${memoData.name}`}
memoName={memoData.name}
onConfirm={handleEditorConfirm}
onCancel={handleEditorCancel}
/>
);
}
return (
<MemoViewContext.Provider value={contextValue}>
<article className={cn(MEMO_CARD_BASE_CLASSES, className)} ref={cardRef} tabIndex={readonly ? -1 : 0}>
<MemoHeader
showCreator={props.showCreator}
showVisibility={props.showVisibility}
showPinned={props.showPinned}
onEdit={openEditor}
onGotoDetail={handleGotoMemoDetailPage}
onUnpin={unpinMemo}
onToggleNsfwVisibility={toggleNsfwVisibility}
reactionSelectorOpen={reactionSelectorOpen}
onReactionSelectorOpenChange={setReactionSelectorOpen}
/>
<MemoBody
compact={props.compact}
onContentClick={handleMemoContentClick}
onContentDoubleClick={handleMemoContentDoubleClick}
onToggleNsfwVisibility={toggleNsfwVisibility}
/>
<PreviewImageDialog
open={previewState.open}
onOpenChange={setPreviewOpen}
imgUrls={previewState.urls}
initialIndex={previewState.index}
/>
</article>
</MemoViewContext.Provider>
);
});
export default memo(MemoView);