Skip to main content
Glama

Karakeep MCP server

by karakeep-app
EditBookmarkDialog.tsx13.8 kB
import * as React from "react"; import { ActionButton } from "@/components/ui/action-button"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/ui/use-toast"; import { useDialogFormReset } from "@/lib/hooks/useDialogFormReset"; import { useTranslation } from "@/lib/i18n/client"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { format } from "date-fns"; import { CalendarIcon } from "lucide-react"; import { useForm } from "react-hook-form"; import { useUpdateBookmark } from "@karakeep/shared-react/hooks/bookmarks"; import { BookmarkTypes, ZBookmark, ZUpdateBookmarksRequest, zUpdateBookmarksRequestSchema, } from "@karakeep/shared/types/bookmarks"; import { getBookmarkTitle } from "@karakeep/shared/utils/bookmarkUtils"; import { BookmarkTagsEditor } from "./BookmarkTagsEditor"; const formSchema = zUpdateBookmarksRequestSchema; export function EditBookmarkDialog({ open, setOpen, bookmark, children, }: { bookmark: ZBookmark; children?: React.ReactNode; open: boolean; setOpen: (v: boolean) => void; }) { const { t } = useTranslation(); const bookmarkToDefault = (bookmark: ZBookmark) => ({ bookmarkId: bookmark.id, summary: bookmark.summary, title: getBookmarkTitle(bookmark), createdAt: bookmark.createdAt ?? new Date(), // Link specific defaults (only if bookmark is a link) url: bookmark.content.type === BookmarkTypes.LINK ? bookmark.content.url : undefined, description: bookmark.content.type === BookmarkTypes.LINK ? (bookmark.content.description ?? "") : undefined, author: bookmark.content.type === BookmarkTypes.LINK ? (bookmark.content.author ?? "") : undefined, publisher: bookmark.content.type === BookmarkTypes.LINK ? (bookmark.content.publisher ?? "") : undefined, datePublished: bookmark.content.type === BookmarkTypes.LINK ? bookmark.content.datePublished : undefined, // Asset specific fields assetContent: bookmark.content.type === BookmarkTypes.ASSET ? bookmark.content.content : undefined, }); const form = useForm<ZUpdateBookmarksRequest>({ resolver: zodResolver(formSchema), defaultValues: bookmarkToDefault(bookmark), }); const { mutate: updateBookmarkMutate, isPending: isUpdatingBookmark } = useUpdateBookmark({ onSuccess: (updatedBookmark) => { toast({ description: "Bookmark details updated successfully!" }); // Close the dialog after successful detail update setOpen(false); // Reset form with potentially updated data form.reset(bookmarkToDefault(updatedBookmark)); }, onError: (error) => { toast({ variant: "destructive", title: "Failed to update bookmark", description: error.message, }); }, }); function onSubmit(values: ZUpdateBookmarksRequest) { // Ensure optional fields that are empty strings are sent as null/undefined if appropriate const payload = { ...values, title: values.title ?? null, }; updateBookmarkMutate(payload); } // Reset form only when dialog is initially opened to preserve unsaved changes // This prevents losing unsaved title edits when tags are updated, which would // cause the bookmark prop to change and trigger a form reset useDialogFormReset(open, form, bookmarkToDefault(bookmark)); const isLink = bookmark.content.type === BookmarkTypes.LINK; const isAsset = bookmark.content.type === BookmarkTypes.ASSET; return ( <Dialog open={open} onOpenChange={setOpen}> {children && <DialogTrigger asChild>{children}</DialogTrigger>} <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-xl"> <DialogHeader> <DialogTitle>{t("bookmark_editor.title")}</DialogTitle> <DialogDescription>{t("bookmark_editor.subtitle")}</DialogDescription> </DialogHeader> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="title" render={({ field }) => ( <FormItem> <FormLabel>{t("common.title")}</FormLabel> <FormControl> <Input placeholder="Bookmark title" {...field} value={field.value ?? ""} /> </FormControl> <FormMessage /> </FormItem> )} /> {isLink && ( <FormField control={form.control} name="url" render={({ field }) => ( <FormItem> <FormLabel>{t("common.url")}</FormLabel> <FormControl> <Input placeholder="https://example.com" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> )} {isLink && ( <FormField control={form.control} name="description" render={({ field }) => ( <FormItem> <FormLabel>{t("common.description")}</FormLabel> <FormControl> <Textarea placeholder="Bookmark description" {...field} value={field.value ?? ""} /> </FormControl> <FormMessage /> </FormItem> )} /> )} {isLink && ( <FormField control={form.control} name="summary" render={({ field }) => ( <FormItem> <FormLabel>{t("common.summary")}</FormLabel> <FormControl> <Textarea placeholder="Bookmark summary" {...field} value={field.value ?? ""} /> </FormControl> <FormMessage /> </FormItem> )} /> )} {isAsset && ( <FormField control={form.control} name="assetContent" render={({ field }) => ( <FormItem> <FormLabel> {t("bookmark_editor.extracted_content")} </FormLabel> <FormControl> <Textarea placeholder="Extracted Content" {...field} value={field.value ?? ""} /> </FormControl> <FormMessage /> </FormItem> )} /> )} {isLink && ( <div className="grid grid-cols-1 gap-4 md:grid-cols-2"> <FormField control={form.control} name="author" render={({ field }) => ( <FormItem> <FormLabel>{t("bookmark_editor.author")}</FormLabel> <FormControl> <Input placeholder="Author name" {...field} value={field.value ?? ""} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="publisher" render={({ field }) => ( <FormItem> <FormLabel>{t("bookmark_editor.publisher")}</FormLabel> <FormControl> <Input placeholder="Publisher name" {...field} value={field.value ?? ""} /> </FormControl> <FormMessage /> </FormItem> )} /> </div> )} <div className="grid grid-cols-1 gap-4 md:grid-cols-2"> <FormField control={form.control} name="createdAt" render={({ field }) => ( <FormItem className="flex flex-col"> <FormLabel>{t("common.created_at")}</FormLabel> <Popover> <PopoverTrigger asChild> <FormControl> <Button variant={"outline"} className={cn( "pl-3 text-left font-normal", !field.value && "text-muted-foreground", )} > {field.value ? ( format(field.value, "PPP") ) : ( <span>{t("bookmark_editor.pick_a_date")}</span> )} <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> </Button> </FormControl> </PopoverTrigger> <PopoverContent className="w-auto p-0" align="start"> <Calendar mode="single" selected={field.value} onSelect={field.onChange} disabled={(date) => date > new Date() || date < new Date("1900-01-01") } /> </PopoverContent> </Popover> <FormMessage /> </FormItem> )} /> {isLink && ( <FormField control={form.control} name="datePublished" render={({ field }) => ( <FormItem className="flex flex-col"> <FormLabel> {t("bookmark_editor.date_published")} </FormLabel> <Popover> <PopoverTrigger asChild> <FormControl> <Button variant={"outline"} className={cn( "pl-3 text-left font-normal", !field.value && "text-muted-foreground", )} > {field.value ? ( format(field.value, "PPP") ) : ( <span>{t("bookmark_editor.pick_a_date")}</span> )} <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> </Button> </FormControl> </PopoverTrigger> <PopoverContent className="w-auto p-0" align="start"> <Calendar mode="single" selected={field.value ?? undefined} // Calendar expects Date | undefined onSelect={(date) => field.onChange(date ?? null)} // Handle undefined -> null disabled={(date) => date > new Date() || date < new Date("1900-01-01") } /> </PopoverContent> </Popover> <FormMessage /> </FormItem> )} /> )} </div> <FormItem> <FormLabel>{t("common.tags")}</FormLabel> <FormControl> <BookmarkTagsEditor bookmark={bookmark} /> </FormControl> <FormMessage /> </FormItem> <DialogFooter> <Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={isUpdatingBookmark} > {t("actions.cancel")} </Button> <ActionButton type="submit" loading={isUpdatingBookmark}> {t("bookmark_editor.save_changes")} </ActionButton> </DialogFooter> </form> </Form> </DialogContent> </Dialog> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/karakeep-app/karakeep'

If you have feedback or need assistance with the MCP directory API, please join our Discord server