122 lines
5.1 KiB
TypeScript
122 lines
5.1 KiB
TypeScript
import { Edit3Icon, MoreVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import toast from "react-hot-toast";
|
|
import { useNavigate } from "react-router-dom";
|
|
import ConfirmDialog from "@/components/ConfirmDialog";
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
import { shortcutServiceClient } from "@/connect";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
|
|
import { cn } from "@/lib/utils";
|
|
import { ROUTES } from "@/router";
|
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service_pb";
|
|
import { useTranslate } from "@/utils/i18n";
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
|
|
|
|
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
|
|
|
|
// Helper function to extract shortcut ID from resource name
|
|
// Format: users/{username}/shortcuts/{shortcut}
|
|
const getShortcutId = (name: string): string => {
|
|
const parts = name.split("/");
|
|
return parts.length === 4 ? parts[3] : "";
|
|
};
|
|
|
|
function ShortcutsSection() {
|
|
const t = useTranslate();
|
|
const navigate = useNavigate();
|
|
const { shortcuts, refetchSettings } = useAuth();
|
|
const { shortcut: selectedShortcut, setShortcut } = useMemoFilterContext();
|
|
const [deleteTarget, setDeleteTarget] = useState<Shortcut | undefined>();
|
|
|
|
useEffect(() => {
|
|
refetchSettings();
|
|
}, [refetchSettings]);
|
|
|
|
const handleDeleteShortcut = async (shortcut: Shortcut) => {
|
|
setDeleteTarget(shortcut);
|
|
};
|
|
|
|
const confirmDeleteShortcut = async () => {
|
|
if (!deleteTarget) return;
|
|
await shortcutServiceClient.deleteShortcut({ name: deleteTarget.name });
|
|
await refetchSettings();
|
|
toast.success(t("setting.shortcut.delete-success", { title: deleteTarget.title }));
|
|
setDeleteTarget(undefined);
|
|
};
|
|
|
|
const handleCreateShortcut = () => {
|
|
navigate(ROUTES.SHORTCUTS, { state: { openCreate: true } });
|
|
};
|
|
|
|
const handleEditShortcut = (shortcut: Shortcut) => {
|
|
navigate(ROUTES.SHORTCUTS, { state: { shortcut } });
|
|
};
|
|
|
|
return (
|
|
<div className="w-full flex flex-col justify-start items-start mt-3 px-1 h-auto shrink-0 flex-nowrap">
|
|
<div className="flex flex-row justify-between items-center w-full gap-1 mb-1 text-sm leading-6 text-muted-foreground select-none">
|
|
<span>{t("common.shortcuts")}</span>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<PlusIcon className="w-4 h-auto cursor-pointer" onClick={handleCreateShortcut} />
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>{t("common.create")}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
|
|
{shortcuts.map((shortcut) => {
|
|
const shortcutId = getShortcutId(shortcut.name);
|
|
const maybeEmoji = shortcut.title.split(" ")[0];
|
|
const emoji = emojiRegex.test(maybeEmoji) ? maybeEmoji : undefined;
|
|
const title = emoji ? shortcut.title.replace(emoji, "") : shortcut.title;
|
|
const selected = selectedShortcut === shortcutId;
|
|
return (
|
|
<div
|
|
key={shortcutId}
|
|
className="shrink-0 w-full text-sm rounded-md leading-6 flex flex-row justify-between items-center select-none gap-2 text-muted-foreground"
|
|
>
|
|
<span
|
|
className={cn("truncate cursor-pointer text-muted-foreground", selected && "text-primary font-medium")}
|
|
onClick={() => (selected ? setShortcut(undefined) : setShortcut(shortcutId))}
|
|
>
|
|
{emoji && <span className="text-base mr-1">{emoji}</span>}
|
|
{title.trim()}
|
|
</span>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<MoreVerticalIcon className="w-4 h-auto shrink-0 text-muted-foreground cursor-pointer hover:text-foreground" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" alignOffset={-12}>
|
|
<DropdownMenuItem onClick={() => handleEditShortcut(shortcut)}>
|
|
<Edit3Icon className="w-4 h-auto" />
|
|
{t("common.edit")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => handleDeleteShortcut(shortcut)}>
|
|
<TrashIcon className="w-4 h-auto" />
|
|
{t("common.delete")}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<ConfirmDialog
|
|
open={!!deleteTarget}
|
|
onOpenChange={(open) => !open && setDeleteTarget(undefined)}
|
|
title={t("setting.shortcut.delete-confirm", { title: deleteTarget?.title ?? "" })}
|
|
confirmLabel={t("common.delete")}
|
|
cancelLabel={t("common.cancel")}
|
|
onConfirm={confirmDeleteShortcut}
|
|
confirmVariant="destructive"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default ShortcutsSection;
|