import React, { ChangeEvent, HTMLProps, KeyboardEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { createMarkPositioner, LinkExtension, ShortcutHandlerProps } from "remirror/extensions"; // buttons import { ToggleBoldButton, ToggleItalicButton, ToggleUnderlineButton, ToggleStrikeButton, ToggleOrderedListButton, ToggleBulletListButton, ToggleCodeButton, ToggleHeadingButton, useActive, CommandButton, useAttrs, useChainedCommands, useCurrentSelection, useExtensionEvent, useUpdateReason, } from "@remirror/react"; import { EditorState } from "remirror"; type Props = { gptOption?: boolean; editorState: Readonly; setDisableToolbar: React.Dispatch>; }; const useLinkShortcut = () => { const [linkShortcut, setLinkShortcut] = useState(); const [isEditing, setIsEditing] = useState(false); useExtensionEvent( LinkExtension, "onShortcut", useCallback( (props) => { if (!isEditing) { setIsEditing(true); } return setLinkShortcut(props); }, [isEditing] ) ); return { linkShortcut, isEditing, setIsEditing }; }; const useFloatingLinkState = () => { const chain = useChainedCommands(); const { isEditing, linkShortcut, setIsEditing } = useLinkShortcut(); const { to, empty } = useCurrentSelection(); const url = (useAttrs().link()?.href as string) ?? ""; const [href, setHref] = useState(url); // A positioner which only shows for links. const linkPositioner = useMemo(() => createMarkPositioner({ type: "link" }), []); const onRemove = useCallback(() => chain.removeLink().focus().run(), [chain]); const updateReason = useUpdateReason(); useLayoutEffect(() => { if (!isEditing) { return; } if (updateReason.doc || updateReason.selection) { setIsEditing(false); } }, [isEditing, setIsEditing, updateReason.doc, updateReason.selection]); useEffect(() => { setHref(url); }, [url]); const submitHref = useCallback(() => { setIsEditing(false); const range = linkShortcut ?? undefined; if (href === "") { chain.removeLink(); } else { chain.updateLink({ href, auto: false }, range); } chain.focus(range?.to ?? to).run(); }, [setIsEditing, linkShortcut, chain, href, to]); const cancelHref = useCallback(() => { setIsEditing(false); }, [setIsEditing]); const clickEdit = useCallback(() => { if (empty) { chain.selectLink(); } setIsEditing(true); }, [chain, empty, setIsEditing]); return useMemo( () => ({ href, setHref, linkShortcut, linkPositioner, isEditing, setIsEditing, clickEdit, onRemove, submitHref, cancelHref, }), [ href, linkShortcut, linkPositioner, isEditing, clickEdit, onRemove, submitHref, cancelHref, setIsEditing, ] ); }; const DelayAutoFocusInput = ({ autoFocus, setDisableToolbar, ...rest }: HTMLProps & { setDisableToolbar: React.Dispatch>; }) => { const inputRef = useRef(null); useEffect(() => { if (!autoFocus) { return; } setDisableToolbar(false); const frame = window.requestAnimationFrame(() => { inputRef.current?.focus(); }); return () => { window.cancelAnimationFrame(frame); }; }, [autoFocus, setDisableToolbar]); useEffect(() => { setDisableToolbar(false); }, [setDisableToolbar]); return ( <> { if (rest.onKeyDown) rest.onKeyDown(e); setDisableToolbar(false); }} className={`${rest.className} mt-1`} onFocus={() => { setDisableToolbar(false); }} onBlur={() => { setDisableToolbar(true); }} /> ); }; export const CustomFloatingToolbar: React.FC = ({ gptOption, editorState, setDisableToolbar, }) => { const { isEditing, setIsEditing, clickEdit, onRemove, submitHref, href, setHref, cancelHref } = useFloatingLinkState(); const active = useActive(); const activeLink = active.link(); const handleClickEdit = useCallback(() => { clickEdit(); }, [clickEdit]); return (
{gptOption && (
)}
{activeLink ? (
{ window.open(href, "_blank"); }} icon="externalLinkFill" enabled />
) : ( { if (isEditing) { setIsEditing(false); } else { handleClickEdit(); } }} icon="link" enabled active={isEditing} /> )}
{isEditing && (
) => setHref(e.target.value)} value={href} onKeyDown={(e: KeyboardEvent) => { const { code } = e; if (code === "Enter") { submitHref(); } if (code === "Escape") { cancelHref(); } }} />
)}
); };