mirror of https://github.com/usememos/memos.git
fix: auth checks in reaction selector
This commit is contained in:
parent
ef8e3cfb99
commit
d2acebcc53
|
|
@ -1,22 +1,28 @@
|
|||
import { memo, useMemo, useRef, useState } from "react";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useUser } from "@/hooks/useUserQueries";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { State } from "@/types/proto/api/v1/common_pb";
|
||||
import { isSuperUser } from "@/utils/user";
|
||||
import MemoEditor from "../MemoEditor";
|
||||
import PreviewImageDialog from "../PreviewImageDialog";
|
||||
import { MemoBody, MemoHeader } from "./components";
|
||||
import { MEMO_CARD_BASE_CLASSES } from "./constants";
|
||||
import { useImagePreview, useMemoActions, useMemoHandlers, useMemoViewDerivedState, useNsfwContent } from "./hooks";
|
||||
import { useImagePreview, useMemoActions, useMemoHandlers, useNsfwContent } from "./hooks";
|
||||
import { MemoViewContext } from "./MemoViewContext";
|
||||
import type { MemoViewProps } from "./types";
|
||||
|
||||
const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
||||
const { memo: memoData, className } = props;
|
||||
const { memo: memoData, className, parentPage: parentPageProp } = props;
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
|
||||
const currentUser = useCurrentUser();
|
||||
const creator = useUser(memoData.creator).data;
|
||||
const { isArchived, readonly, parentPage } = useMemoViewDerivedState(memoData, props.parentPage);
|
||||
const isArchived = memoData.state === State.ARCHIVED;
|
||||
const readonly = memoData.creator !== currentUser?.name && !isSuperUser(currentUser);
|
||||
const parentPage = parentPageProp || "/";
|
||||
|
||||
const { nsfw, showNSFWContent, toggleNsfwVisibility } = useNsfwContent(memoData, props.showNsfwContent);
|
||||
const { previewState, openPreview, setPreviewOpen } = useImagePreview();
|
||||
const { unpinMemo } = useMemoActions(memoData, isArchived);
|
||||
|
|
@ -37,11 +43,14 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
|||
() => ({
|
||||
memo: memoData,
|
||||
creator,
|
||||
currentUser,
|
||||
parentPage,
|
||||
isArchived,
|
||||
readonly,
|
||||
showNSFWContent,
|
||||
nsfw,
|
||||
}),
|
||||
[memoData, creator, parentPage, showNSFWContent, nsfw],
|
||||
[memoData, creator, currentUser, parentPage, isArchived, readonly, showNSFWContent, nsfw],
|
||||
);
|
||||
|
||||
if (showEditor) {
|
||||
|
|
@ -68,8 +77,6 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
|||
onGotoDetail={handleGotoMemoDetailPage}
|
||||
onUnpin={unpinMemo}
|
||||
onToggleNsfwVisibility={toggleNsfwVisibility}
|
||||
reactionSelectorOpen={reactionSelectorOpen}
|
||||
onReactionSelectorOpenChange={setReactionSelectorOpen}
|
||||
/>
|
||||
|
||||
<MemoBody
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
||||
import { createContext, useContext } from "react";
|
||||
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 type { User } from "@/types/proto/api/v1/user_service_pb";
|
||||
import { isSuperUser } from "@/utils/user";
|
||||
import { RELATIVE_TIME_THRESHOLD_MS } from "./constants";
|
||||
|
||||
export interface MemoViewContextValue {
|
||||
memo: Memo;
|
||||
creator: User | undefined;
|
||||
currentUser: User | undefined;
|
||||
parentPage: string;
|
||||
isArchived: boolean;
|
||||
readonly: boolean;
|
||||
showNSFWContent: boolean;
|
||||
nsfw: boolean;
|
||||
}
|
||||
|
|
@ -28,12 +28,9 @@ export const useMemoViewContext = (): MemoViewContextValue => {
|
|||
};
|
||||
|
||||
export const useMemoViewDerived = () => {
|
||||
const { memo } = useMemoViewContext();
|
||||
const { memo, isArchived, readonly } = useMemoViewContext();
|
||||
const location = useLocation();
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
const isArchived = memo.state === State.ARCHIVED;
|
||||
const readonly = memo.creator !== currentUser?.name && !isSuperUser(currentUser);
|
||||
const isInMemoDetailPage = location.pathname.startsWith(`/${memo.name}`);
|
||||
|
||||
const commentAmount = memo.relations.filter(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
||||
import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import i18n from "@/i18n";
|
||||
|
|
@ -23,13 +24,12 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({
|
|||
onGotoDetail,
|
||||
onUnpin,
|
||||
onToggleNsfwVisibility,
|
||||
reactionSelectorOpen,
|
||||
onReactionSelectorOpenChange,
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
|
||||
|
||||
const { memo, creator, parentPage, showNSFWContent, nsfw } = useMemoViewContext();
|
||||
const { isArchived, readonly, isInMemoDetailPage, commentAmount, relativeTimeFormat } = useMemoViewDerived();
|
||||
const { memo, creator, currentUser, parentPage, isArchived, readonly, showNSFWContent, nsfw } = useMemoViewContext();
|
||||
const { isInMemoDetailPage, commentAmount, relativeTimeFormat } = useMemoViewDerived();
|
||||
|
||||
const displayTime = isArchived ? (
|
||||
(memo.displayTime ? timestampDate(memo.displayTime) : undefined)?.toLocaleString(i18n.language)
|
||||
|
|
@ -43,7 +43,6 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({
|
|||
|
||||
return (
|
||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||
{/* Left section: Creator info or time */}
|
||||
<div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center">
|
||||
{showCreator && creator ? (
|
||||
<CreatorDisplay creator={creator} displayTime={displayTime} onGotoDetail={onGotoDetail} />
|
||||
|
|
@ -52,18 +51,15 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Right section: Actions */}
|
||||
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
|
||||
{/* Reaction selector */}
|
||||
{!isArchived && (
|
||||
{currentUser && !isArchived && (
|
||||
<ReactionSelector
|
||||
className={cn("border-none w-auto h-auto", reactionSelectorOpen && "block!", "hidden group-hover:block")}
|
||||
memo={memo}
|
||||
onOpenChange={onReactionSelectorOpenChange}
|
||||
onOpenChange={setReactionSelectorOpen}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Comment count link */}
|
||||
{!isInMemoDetailPage && (
|
||||
<Link
|
||||
className={cn(
|
||||
|
|
@ -79,7 +75,6 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({
|
|||
</Link>
|
||||
)}
|
||||
|
||||
{/* Visibility icon */}
|
||||
{showVisibility && memo.visibility !== Visibility.PRIVATE && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
|
|
@ -93,7 +88,6 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({
|
|||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Pinned indicator */}
|
||||
{showPinned && memo.pinned && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
|
|
@ -109,14 +103,12 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({
|
|||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{/* NSFW hide button */}
|
||||
{nsfw && showNSFWContent && onToggleNsfwVisibility && (
|
||||
<span className="cursor-pointer">
|
||||
<EyeOffIcon className="w-4 h-auto text-primary" onClick={onToggleNsfwVisibility} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Action menu */}
|
||||
<MemoActionMenu memo={memo} readonly={readonly} onEdit={onEdit} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
export { useImagePreview } from "./useImagePreview";
|
||||
export { useMemoActions } from "./useMemoActions";
|
||||
export { useMemoHandlers } from "./useMemoHandlers";
|
||||
export { useMemoViewDerivedState } from "./useMemoViewDerivedState";
|
||||
export { useNsfwContent } from "./useNsfwContent";
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
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 { isSuperUser } from "@/utils/user";
|
||||
|
||||
export const useMemoViewDerivedState = (memo: Memo, parentPageProp?: string) => {
|
||||
const location = useLocation();
|
||||
const user = useCurrentUser();
|
||||
|
||||
const isArchived = memo.state === State.ARCHIVED;
|
||||
const readonly = memo.creator !== user?.name && !isSuperUser(user);
|
||||
const parentPage = parentPageProp || location.pathname;
|
||||
|
||||
return { isArchived, readonly, parentPage };
|
||||
};
|
||||
|
|
@ -1,56 +1,28 @@
|
|||
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
|
||||
|
||||
/**
|
||||
* Props for the MemoView component.
|
||||
* MemoView is the main component for displaying a memo card with all its metadata,
|
||||
* content, and interactive elements.
|
||||
*/
|
||||
export interface MemoViewProps {
|
||||
/** The memo object to display */
|
||||
memo: Memo;
|
||||
/** Whether to show compact view (hides some metadata) */
|
||||
compact?: boolean;
|
||||
/** Whether to show the creator's profile information */
|
||||
showCreator?: boolean;
|
||||
/** Whether to show the visibility indicator */
|
||||
showVisibility?: boolean;
|
||||
/** Whether to show the pinned indicator */
|
||||
showPinned?: boolean;
|
||||
/** Whether to show NSFW content by default */
|
||||
showNsfwContent?: boolean;
|
||||
/** Additional CSS classes to apply to the root element */
|
||||
className?: string;
|
||||
/** The parent page URL for navigation context */
|
||||
parentPage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the MemoHeader component.
|
||||
* Displays memo metadata like creator, timestamp, and action buttons.
|
||||
*/
|
||||
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.
|
||||
* Displays memo content, attachments, and relations.
|
||||
*/
|
||||
export interface MemoBodyProps {
|
||||
// Display options
|
||||
compact?: boolean;
|
||||
// Callbacks
|
||||
onContentClick: (e: React.MouseEvent) => void;
|
||||
onContentDoubleClick: (e: React.MouseEvent) => void;
|
||||
onToggleNsfwVisibility: () => void;
|
||||
|
|
|
|||
Loading…
Reference in New Issue