166 lines
5.5 KiB
TypeScript
166 lines
5.5 KiB
TypeScript
import {
|
|
ArchiveIcon,
|
|
ArchiveRestoreIcon,
|
|
BookmarkMinusIcon,
|
|
BookmarkPlusIcon,
|
|
CheckCheckIcon,
|
|
CopyIcon,
|
|
Edit3Icon,
|
|
FileTextIcon,
|
|
LinkIcon,
|
|
ListChecksIcon,
|
|
ListRestartIcon,
|
|
MoreVerticalIcon,
|
|
TrashIcon,
|
|
} from "lucide-react";
|
|
import { useState } from "react";
|
|
import ConfirmDialog from "@/components/ConfirmDialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { State } from "@/types/proto/api/v1/common_pb";
|
|
import { useTranslate } from "@/utils/i18n";
|
|
import { countTasks } from "@/utils/markdown-manipulation";
|
|
import { useMemoActionHandlers } from "./hooks";
|
|
import type { MemoActionMenuProps } from "./types";
|
|
|
|
const MemoActionMenu = (props: MemoActionMenuProps) => {
|
|
const { memo, readonly } = props;
|
|
const t = useTranslate();
|
|
|
|
// Dialog state
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
|
|
// Derived state
|
|
const isComment = Boolean(memo.parent);
|
|
const isArchived = memo.state === State.ARCHIVED;
|
|
const taskStats = countTasks(memo.content);
|
|
const canMutateTasks = !readonly && !isArchived && taskStats.total > 0;
|
|
const hasOpenTasks = taskStats.completed < taskStats.total;
|
|
const hasCompletedTasks = taskStats.completed > 0;
|
|
|
|
// Action handlers
|
|
const {
|
|
handleTogglePinMemoBtnClick,
|
|
handleEditMemoClick,
|
|
handleToggleMemoStatusClick,
|
|
handleCopyLink,
|
|
handleCopyContent,
|
|
handleCheckAllTaskListItemsClick,
|
|
handleUncheckAllTaskListItemsClick,
|
|
handleDeleteMemoClick,
|
|
confirmDeleteMemo,
|
|
} = useMemoActionHandlers({
|
|
memo,
|
|
onEdit: props.onEdit,
|
|
setDeleteDialogOpen,
|
|
});
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="size-4">
|
|
<MoreVerticalIcon className="text-muted-foreground" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" sideOffset={2}>
|
|
{/* Edit actions (non-readonly, non-archived) */}
|
|
{!readonly && !isArchived && (
|
|
<>
|
|
{!isComment && (
|
|
<DropdownMenuItem onClick={handleTogglePinMemoBtnClick}>
|
|
{memo.pinned ? <BookmarkMinusIcon className="w-4 h-auto" /> : <BookmarkPlusIcon className="w-4 h-auto" />}
|
|
{memo.pinned ? t("common.unpin") : t("common.pin")}
|
|
</DropdownMenuItem>
|
|
)}
|
|
<DropdownMenuItem onClick={handleEditMemoClick}>
|
|
<Edit3Icon className="w-4 h-auto" />
|
|
{t("common.edit")}
|
|
</DropdownMenuItem>
|
|
</>
|
|
)}
|
|
|
|
{/* Copy submenu (non-archived) */}
|
|
{!isArchived && (
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>
|
|
<CopyIcon className="w-4 h-auto" />
|
|
{t("common.copy")}
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
<DropdownMenuItem onClick={handleCopyLink}>
|
|
<LinkIcon className="w-4 h-auto" />
|
|
{t("memo.copy-link")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={handleCopyContent}>
|
|
<FileTextIcon className="w-4 h-auto" />
|
|
{t("memo.copy-content")}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
)}
|
|
|
|
{/* Task submenu (writable task memos) */}
|
|
{canMutateTasks && (
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>
|
|
<ListChecksIcon className="w-4 h-auto" />
|
|
{t("memo.task-actions.title")}
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
<DropdownMenuItem disabled={!hasOpenTasks} onClick={handleCheckAllTaskListItemsClick}>
|
|
<CheckCheckIcon className="w-4 h-auto" />
|
|
{t("memo.task-actions.check-all")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem disabled={!hasCompletedTasks} onClick={handleUncheckAllTaskListItemsClick}>
|
|
<ListRestartIcon className="w-4 h-auto" />
|
|
{t("memo.task-actions.uncheck-all")}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
)}
|
|
|
|
{/* Write actions (non-readonly) */}
|
|
{!readonly && (
|
|
<>
|
|
{/* Archive/Restore (non-comment) */}
|
|
{!isComment && (
|
|
<DropdownMenuItem onClick={handleToggleMemoStatusClick}>
|
|
{isArchived ? <ArchiveRestoreIcon className="w-4 h-auto" /> : <ArchiveIcon className="w-4 h-auto" />}
|
|
{isArchived ? t("common.restore") : t("common.archive")}
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
{/* Delete */}
|
|
<DropdownMenuItem onClick={handleDeleteMemoClick}>
|
|
<TrashIcon className="w-4 h-auto" />
|
|
{t("common.delete")}
|
|
</DropdownMenuItem>
|
|
</>
|
|
)}
|
|
</DropdownMenuContent>
|
|
|
|
{/* Delete confirmation dialog */}
|
|
<ConfirmDialog
|
|
open={deleteDialogOpen}
|
|
onOpenChange={setDeleteDialogOpen}
|
|
title={t("memo.delete-confirm")}
|
|
confirmLabel={t("common.delete")}
|
|
description={t("memo.delete-confirm-description")}
|
|
cancelLabel={t("common.cancel")}
|
|
onConfirm={confirmDeleteMemo}
|
|
confirmVariant="destructive"
|
|
/>
|
|
</DropdownMenu>
|
|
);
|
|
};
|
|
|
|
export default MemoActionMenu;
|