新增详情菜单项

This commit is contained in:
anian
2026-07-04 18:04:51 +08:00
parent 70ab5ef059
commit edd2867a47
5 changed files with 62 additions and 17 deletions
@@ -1,4 +1,5 @@
import {
ArrowLeftIcon,
ArchiveIcon,
ArchiveRestoreIcon,
BookmarkMinusIcon,
@@ -6,6 +7,7 @@ import {
CheckCheckIcon,
CopyIcon,
Edit3Icon,
EyeIcon,
FileTextIcon,
LinkIcon,
ListChecksIcon,
@@ -32,7 +34,7 @@ import { useMemoActionHandlers } from "./hooks";
import type { MemoActionMenuProps } from "./types";
const MemoActionMenu = (props: MemoActionMenuProps) => {
const { memo, readonly } = props;
const { memo, readonly, isInMemoDetailPage, onBack, onViewDetail } = props;
const t = useTranslate();
// Dialog state
@@ -45,6 +47,8 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
const canMutateTasks = !readonly && !isArchived && taskStats.total > 0;
const hasOpenTasks = taskStats.completed < taskStats.total;
const hasCompletedTasks = taskStats.completed > 0;
const DetailActionIcon = isInMemoDetailPage ? ArrowLeftIcon : EyeIcon;
const detailActionLabel = isInMemoDetailPage ? t("common.collapse") : t("memo.view-detail");
// Action handlers
const {
@@ -71,6 +75,11 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={2}>
<DropdownMenuItem onClick={isInMemoDetailPage ? onBack : onViewDetail}>
<DetailActionIcon className="w-4 h-auto" />
{detailActionLabel}
</DropdownMenuItem>
{/* Edit actions (non-readonly) */}
{!readonly && (
<>
@@ -4,5 +4,8 @@ export interface MemoActionMenuProps {
memo: Memo;
readonly?: boolean;
className?: string;
isInMemoDetailPage: boolean;
onBack: () => void;
onViewDetail: () => void;
onEdit?: () => void;
}
+4 -3
View File
@@ -27,7 +27,10 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const creator = useUser(memoData.creator).data;
const isArchived = memoData.state === State.ARCHIVED;
const readonly = memoData.creator !== currentUser?.name && !isSuperUser(currentUser);
const parentPage = parentPageProp || "/";
const location = useLocation();
const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`) || location.pathname.startsWith("/memos/shares/");
const currentPage = `${location.pathname}${location.search}${location.hash}`;
const parentPage = parentPageProp || (isInMemoDetailPage ? "/" : currentPage);
// Blur content when any tag has blur_content enabled in the instance tag settings.
const [showBlurredContent, setShowBlurredContent] = useState(false);
@@ -39,8 +42,6 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const openEditor = useCallback(() => setShowEditor(true), []);
const closeEditor = useCallback(() => setShowEditor(false), []);
const location = useLocation();
const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`) || location.pathname.startsWith("/memos/shares/");
const showCommentPreview = !isInMemoDetailPage && computeCommentAmount(memoData) > 0;
useEffect(() => {
@@ -1,6 +1,6 @@
import { ArchiveIcon, BookmarkIcon } from "lucide-react";
import { useCallback, useState } from "react";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import useNavigateTo from "@/hooks/useNavigateTo";
import i18n from "@/i18n";
@@ -20,12 +20,37 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext();
const { createTime, updateTime, displayTime: memoDisplayTime, isDisplayingUpdatedTime, relativeTimeFormat } = useMemoViewDerived();
const {
createTime,
updateTime,
displayTime: memoDisplayTime,
isDisplayingUpdatedTime,
isInMemoDetailPage,
relativeTimeFormat,
} = useMemoViewDerived();
const navigateTo = useNavigateTo();
const navigate = useNavigate();
const handleGotoMemoDetailPage = useCallback(() => {
navigateTo(`/${memo.name}`, { state: { from: parentPage } });
}, [memo.name, parentPage, navigateTo]);
const handleBackToParentPage = useCallback(() => {
const historyState = window.history.state as { idx?: number } | null;
if (typeof historyState?.idx === "number" && historyState.idx > 0) {
navigate(-1);
return;
}
navigateTo(parentPage, { preventScrollReset: true });
}, [parentPage, navigate, navigateTo]);
const handleMemoTimeClick = useCallback(() => {
if (isInMemoDetailPage) {
handleBackToParentPage();
return;
}
handleGotoMemoDetailPage();
}, [isInMemoDetailPage, handleBackToParentPage, handleGotoMemoDetailPage]);
const timeValue = isArchived ? (
memoDisplayTime?.toLocaleString(i18n.language)
@@ -48,9 +73,9 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
<div className="w-full flex flex-row justify-between items-center gap-2">
<div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center">
{showCreator && creator ? (
<CreatorDisplay creator={creator} displayTime={displayTime} timeTooltip={timeTooltip} onGotoDetail={handleGotoMemoDetailPage} />
<CreatorDisplay creator={creator} displayTime={displayTime} timeTooltip={timeTooltip} onTimeClick={handleMemoTimeClick} />
) : (
<TimeDisplay displayTime={displayTime} timeTooltip={timeTooltip} onGotoDetail={handleGotoMemoDetailPage} />
<TimeDisplay displayTime={displayTime} timeTooltip={timeTooltip} onTimeClick={handleMemoTimeClick} />
)}
</div>
@@ -102,7 +127,14 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
</Tooltip>
)}
<MemoActionMenu memo={memo} readonly={readonly} onEdit={openEditor} />
<MemoActionMenu
memo={memo}
readonly={readonly}
isInMemoDetailPage={isInMemoDetailPage}
onBack={handleBackToParentPage}
onViewDetail={handleGotoMemoDetailPage}
onEdit={openEditor}
/>
</div>
</div>
);
@@ -112,10 +144,10 @@ interface CreatorDisplayProps {
creator: User;
displayTime: React.ReactNode;
timeTooltip: TimeTooltipContent;
onGotoDetail: () => void;
onTimeClick: () => void;
}
const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, timeTooltip, onGotoDetail }) => (
const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, timeTooltip, onTimeClick }) => (
<div className="w-full flex flex-row justify-start items-center">
<Link className="w-auto hover:opacity-80 rounded-md transition-colors" to={`/u/${encodeURIComponent(creator.username)}`} viewTransition>
<UserAvatar className="mr-2 shrink-0" avatarUrl={creator.avatarUrl} />
@@ -132,7 +164,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, t
<button
type="button"
className="w-auto -mt-0.5 text-xs leading-tight text-muted-foreground select-none cursor-pointer hover:opacity-80 transition-colors text-left"
onClick={onGotoDetail}
onClick={onTimeClick}
>
{displayTime}
</button>
@@ -159,15 +191,15 @@ const TimeTooltip = ({ children, content }: { children: React.ReactElement; cont
interface TimeDisplayProps {
displayTime: React.ReactNode;
timeTooltip: TimeTooltipContent;
onGotoDetail: () => void;
onTimeClick: () => void;
}
const TimeDisplay: React.FC<TimeDisplayProps> = ({ displayTime, timeTooltip, onGotoDetail }) => (
const TimeDisplay: React.FC<TimeDisplayProps> = ({ displayTime, timeTooltip, onTimeClick }) => (
<TimeTooltip content={timeTooltip}>
<button
type="button"
className="w-auto text-sm leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors text-left"
onClick={onGotoDetail}
onClick={onTimeClick}
>
{displayTime}
</button>