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();
}
}}
/>
>
);
};