import React, { ChangeEvent, HTMLProps, KeyboardEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { createMarkPositioner, LinkExtension, ShortcutHandlerProps } from "remirror/extensions"; import { CommandButton, FloatingToolbar, FloatingWrapper, useActive, useAttrs, useChainedCommands, useCurrentSelection, useExtensionEvent, useUpdateReason, } from "@remirror/react"; 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, clickEdit, onRemove, submitHref, cancelHref, }), [href, linkShortcut, linkPositioner, isEditing, clickEdit, onRemove, submitHref, cancelHref] ); }; const DelayAutoFocusInput = ({ autoFocus, ...rest }: HTMLProps) => { const inputRef = useRef(null); useEffect(() => { if (!autoFocus) { return; } const frame = window.requestAnimationFrame(() => { inputRef.current?.focus(); }); return () => { window.cancelAnimationFrame(frame); }; }, [autoFocus]); return ; }; export const FloatingLinkToolbar = () => { const { isEditing, linkPositioner, clickEdit, onRemove, submitHref, href, setHref, cancelHref } = useFloatingLinkState(); const active = useActive(); const activeLink = active.link(); const { empty } = useCurrentSelection(); const handleClickEdit = useCallback(() => { clickEdit(); }, [clickEdit]); const linkEditButtons = activeLink ? ( <> { window.open(href, "_blank"); }} icon="externalLinkFill" enabled /> ) : ( ); return ( <> {!isEditing && {linkEditButtons}} {!isEditing && empty && ( {linkEditButtons} )} ) => setHref(e.target.value)} value={href} onKeyDown={(e: KeyboardEvent) => { const { code } = e; if (code === "Enter") { submitHref(); } if (code === "Escape") { cancelHref(); } }} /> ); };