新增详情菜单项
This commit is contained in:
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
> 基于原作者v0.29.1代码修改
|
> 基于原作者v0.29.1代码修改
|
||||||
|
|
||||||
#### v0.29.1-2补丁
|
#### v0.29.1-3
|
||||||
1. 修改设置页面commit基础链接
|
1. 修改设置页面commit基础链接
|
||||||
2. 优化图标样式
|
2. 优化图标样式、备忘录菜单跳转
|
||||||
3. 归档页面允许设置表情
|
3. 归档页面允许设置表情
|
||||||
|
|
||||||
#### v0.29.1-2
|
#### v0.29.1-2
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ArrowLeftIcon,
|
||||||
ArchiveIcon,
|
ArchiveIcon,
|
||||||
ArchiveRestoreIcon,
|
ArchiveRestoreIcon,
|
||||||
BookmarkMinusIcon,
|
BookmarkMinusIcon,
|
||||||
@@ -6,6 +7,7 @@ import {
|
|||||||
CheckCheckIcon,
|
CheckCheckIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
Edit3Icon,
|
Edit3Icon,
|
||||||
|
EyeIcon,
|
||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
ListChecksIcon,
|
ListChecksIcon,
|
||||||
@@ -32,7 +34,7 @@ import { useMemoActionHandlers } from "./hooks";
|
|||||||
import type { MemoActionMenuProps } from "./types";
|
import type { MemoActionMenuProps } from "./types";
|
||||||
|
|
||||||
const MemoActionMenu = (props: MemoActionMenuProps) => {
|
const MemoActionMenu = (props: MemoActionMenuProps) => {
|
||||||
const { memo, readonly } = props;
|
const { memo, readonly, isInMemoDetailPage, onBack, onViewDetail } = props;
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
@@ -45,6 +47,8 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
|
|||||||
const canMutateTasks = !readonly && !isArchived && taskStats.total > 0;
|
const canMutateTasks = !readonly && !isArchived && taskStats.total > 0;
|
||||||
const hasOpenTasks = taskStats.completed < taskStats.total;
|
const hasOpenTasks = taskStats.completed < taskStats.total;
|
||||||
const hasCompletedTasks = taskStats.completed > 0;
|
const hasCompletedTasks = taskStats.completed > 0;
|
||||||
|
const DetailActionIcon = isInMemoDetailPage ? ArrowLeftIcon : EyeIcon;
|
||||||
|
const detailActionLabel = isInMemoDetailPage ? t("common.collapse") : t("memo.view-detail");
|
||||||
|
|
||||||
// Action handlers
|
// Action handlers
|
||||||
const {
|
const {
|
||||||
@@ -71,6 +75,11 @@ const MemoActionMenu = (props: MemoActionMenuProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" sideOffset={2}>
|
<DropdownMenuContent align="end" sideOffset={2}>
|
||||||
|
<DropdownMenuItem onClick={isInMemoDetailPage ? onBack : onViewDetail}>
|
||||||
|
<DetailActionIcon className="w-4 h-auto" />
|
||||||
|
{detailActionLabel}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{/* Edit actions (non-readonly) */}
|
{/* Edit actions (non-readonly) */}
|
||||||
{!readonly && (
|
{!readonly && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -4,5 +4,8 @@ export interface MemoActionMenuProps {
|
|||||||
memo: Memo;
|
memo: Memo;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isInMemoDetailPage: boolean;
|
||||||
|
onBack: () => void;
|
||||||
|
onViewDetail: () => void;
|
||||||
onEdit?: () => void;
|
onEdit?: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
|||||||
const creator = useUser(memoData.creator).data;
|
const creator = useUser(memoData.creator).data;
|
||||||
const isArchived = memoData.state === State.ARCHIVED;
|
const isArchived = memoData.state === State.ARCHIVED;
|
||||||
const readonly = memoData.creator !== currentUser?.name && !isSuperUser(currentUser);
|
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.
|
// Blur content when any tag has blur_content enabled in the instance tag settings.
|
||||||
const [showBlurredContent, setShowBlurredContent] = useState(false);
|
const [showBlurredContent, setShowBlurredContent] = useState(false);
|
||||||
@@ -39,8 +42,6 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
|
|||||||
const openEditor = useCallback(() => setShowEditor(true), []);
|
const openEditor = useCallback(() => setShowEditor(true), []);
|
||||||
const closeEditor = useCallback(() => setShowEditor(false), []);
|
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;
|
const showCommentPreview = !isInMemoDetailPage && computeCommentAmount(memoData) > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ArchiveIcon, BookmarkIcon } from "lucide-react";
|
import { ArchiveIcon, BookmarkIcon } from "lucide-react";
|
||||||
import { useCallback, useState } from "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 { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
@@ -20,12 +20,37 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
|||||||
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
|
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
|
||||||
|
|
||||||
const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext();
|
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 navigateTo = useNavigateTo();
|
||||||
|
const navigate = useNavigate();
|
||||||
const handleGotoMemoDetailPage = useCallback(() => {
|
const handleGotoMemoDetailPage = useCallback(() => {
|
||||||
navigateTo(`/${memo.name}`, { state: { from: parentPage } });
|
navigateTo(`/${memo.name}`, { state: { from: parentPage } });
|
||||||
}, [memo.name, parentPage, navigateTo]);
|
}, [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 ? (
|
const timeValue = isArchived ? (
|
||||||
memoDisplayTime?.toLocaleString(i18n.language)
|
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-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">
|
<div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center">
|
||||||
{showCreator && creator ? (
|
{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>
|
</div>
|
||||||
|
|
||||||
@@ -102,7 +127,14 @@ const MemoHeader: React.FC<MemoHeaderProps> = ({ showCreator, showVisibility, sh
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MemoActionMenu memo={memo} readonly={readonly} onEdit={openEditor} />
|
<MemoActionMenu
|
||||||
|
memo={memo}
|
||||||
|
readonly={readonly}
|
||||||
|
isInMemoDetailPage={isInMemoDetailPage}
|
||||||
|
onBack={handleBackToParentPage}
|
||||||
|
onViewDetail={handleGotoMemoDetailPage}
|
||||||
|
onEdit={openEditor}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -112,10 +144,10 @@ interface CreatorDisplayProps {
|
|||||||
creator: User;
|
creator: User;
|
||||||
displayTime: React.ReactNode;
|
displayTime: React.ReactNode;
|
||||||
timeTooltip: TimeTooltipContent;
|
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">
|
<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>
|
<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} />
|
<UserAvatar className="mr-2 shrink-0" avatarUrl={creator.avatarUrl} />
|
||||||
@@ -132,7 +164,7 @@ const CreatorDisplay: React.FC<CreatorDisplayProps> = ({ creator, displayTime, t
|
|||||||
<button
|
<button
|
||||||
type="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"
|
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}
|
{displayTime}
|
||||||
</button>
|
</button>
|
||||||
@@ -159,15 +191,15 @@ const TimeTooltip = ({ children, content }: { children: React.ReactElement; cont
|
|||||||
interface TimeDisplayProps {
|
interface TimeDisplayProps {
|
||||||
displayTime: React.ReactNode;
|
displayTime: React.ReactNode;
|
||||||
timeTooltip: TimeTooltipContent;
|
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}>
|
<TimeTooltip content={timeTooltip}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-auto text-sm leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors text-left"
|
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}
|
{displayTime}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user