mdx-editor / editor

A rich text editor React component for markdown
https://mdxeditor.dev
MIT License
2.07k stars 158 forks source link

Custom Dialog for Inserting Images in MDXEditor #560

Closed yassersaidi closed 3 months ago

yassersaidi commented 3 months ago

I created a custom dialog for inserting images in mdxeditor. This dialog uses Uploadthing for image uploading. Here's the code:

const AddImage = () => {
    const insertImage = usePublisher(insertImage$);
    const [openImageDialog, seOpenImageDialog] = useState(false);
    const [imgUrl, setImgUrl] = useState({
        src: "",
        alt: ""
    });
    const [loading, setLoading] = useState(false);
    const { toast } = useToast();

    return (
        <>
            <Button onClick={() => seOpenImageDialog(true)}>
                <ImageIcon size={20} />
            </Button>

            <Dialog
                open={openImageDialog}
                onOpenChange={() => seOpenImageDialog(false)}
                aria-labelledby="Create Note"
                aria-describedby="Dialog to create new note"
            >
                <DialogContent
                    aria-labelledby="Create Note"
                    aria-describedby="Dialog to create new note"
                    className="flex w-full max-w-[520px] flex-col gap-6 border-none dark:bg-dark-1 bg-light-1 shadow-md px-6 py-9"
                >
                    <div className="flex flex-col gap-2">
                        <div className="my-2 flex flex-col justify-center items-center w-full">
                            <ImagePlus />
                            <p className="mt-1 text-2xl font-semibold">Add Image</p>
                        </div>
                        <UploadDropzone
                            appearance={{
                                button: "bg-ui-yellow px-4 py-2 text-light-text-primary my-2 font-medium cursor-pointer text-base",
                                allowedContent: "my-2 text-sm"
                            }}
                            className="dark:border-dark-border border-light-border"
                            endpoint="imageUploader"
                            config={{
                                appendOnPaste: true,
                            }}
                            onClientUploadComplete={(res) => {
                                res.forEach((file) => {
                                    insertImage({
                                        src: file.url,
                                        altText: file.name,
                                        title: file.name
                                    });
                                });
                                toast({ title: `Upload Complete` });
                                seOpenImageDialog(false);
                            }}
                            onUploadError={(error) => {
                                toast({ title: `ERROR! ${error.message}` });
                            }}
                        />
                        <div className="flex justify-center w-full">
                            <Separator className="w-[80%] my-4 bg-light-border dark:bg-dark-border h-[2px]" />
                        </div>

                        <div className="flex flex-col gap-2">
                            <label htmlFor='image_source' className="font-semibold">
                                Source link:
                            </label>
                            <Input
                                value={imgUrl.src}
                                id="image_source"
                                placeholder='Image Link'
                                className='border-none dark:bg-dark-2 bg-light-2 focus-visible:ring-0 focus-visible:ring-offset-0'
                                onChange={(e) => setImgUrl({ ...imgUrl, src: e.target.value })}
                            />
                        </div>
                        <div className="flex flex-col gap-2">
                            <label htmlFor='image_alt' className="font-semibold">
                                Image title:
                            </label>
                            <Input
                                value={imgUrl.alt}
                                id="image_alt"
                                placeholder='Image Title'
                                className='border-none dark:bg-dark-2 bg-light-2 focus-visible:ring-0 focus-visible:ring-offset-0'
                                onChange={(e) => setImgUrl({ ...imgUrl, alt: e.target.value })}
                            />
                        </div>
                        <div className='flex justify-end items-center w-full gap-2'>
                            <Button
                                className="dark:disabled:bg-dark-2/90 dark:bg-dark-2 bg-gray-300/90 disabled:bg-light-3 hover:opacity-90 focus-visible:ring-0 focus-visible:ring-offset-0"
                                onClick={() => seOpenImageDialog(false)}
                            >
                                Cancel
                            </Button>
                            <Button
                                disabled={loading || imgUrl.alt.trim().length === 0 || imgUrl.src.trim().length === 0}
                                className="text-dark-text-primary disabled:bg-green-1/90 bg-green-1  hover:opacity-90 focus-visible:ring-0 focus-visible:ring-offset-0"
                                onClick={async () => {
                                    try {
                                        if (imgUrl.alt.trim().length === 0 || imgUrl.src.trim().length === 0) {
                                            return;
                                        }
                                        setLoading(true);
                                        await insertImage({
                                            src: imgUrl.src,
                                            altText: imgUrl.alt,
                                            title: imgUrl.alt
                                        });
                                        setLoading(false);
                                        seOpenImageDialog(false);
                                    } catch {
                                        setLoading(false);
                                        toast({ title: "Can't Add your Image, try again." });
                                    }
                                }}
                            >
                                {loading ? (
                                    <>
                                        <Image
                                            width={28}
                                            height={28}
                                            src="/icons/loading-circle.svg"
                                            alt="Loading"
                                            className='mr-2'
                                        />
                                        Saving
                                    </>
                                ) : (
                                    <>Save</>
                                )}
                            </Button>
                        </div>
                    </div>
                </DialogContent>
            </Dialog>
        </>
    );
};
toolbarContents: () => (
    <>
        <DiffSourceToggleWrapper>
            <UndoRedo />
            <BoldItalicUnderlineToggles />
            <BlockTypeSelect />
            <Separator />
            <CreateLink />
            <AddImage /> {/* Add it here */}
            <InsertTable />
            <ListsToggle />
            <InsertAdmonition />
            <InsertThematicBreak />
            <YouTubeButton />
        </DiffSourceToggleWrapper>
    </>
);

This code defines a new custom dialog component, AddImage, which allows users to insert images into the editor. The dialog includes both an upload dropzone (using Uploadthing) and fields for manually entering image source and title. Add the component to your toolbarContents to include it in the editor's toolbar.

image