fix: insert link for selected text behavior (#858)

This commit is contained in:
Daniel Lautzenheiser
2023-08-31 14:00:14 -04:00
committed by GitHub
parent 1fee80046f
commit 316c8b62a3
3 changed files with 30 additions and 27 deletions

View File

@ -18,10 +18,10 @@ export interface BaseBaseProps {
endIcon?: FC<{ className?: string }>; endIcon?: FC<{ className?: string }>;
title?: string; title?: string;
'data-testid'?: string; 'data-testid'?: string;
onClick?: MouseEventHandler;
} }
export interface ButtonProps extends BaseBaseProps { export interface ButtonProps extends BaseBaseProps {
onClick?: MouseEventHandler;
disabled?: boolean; disabled?: boolean;
buttonRef?: Ref<HTMLButtonElement>; buttonRef?: Ref<HTMLButtonElement>;
'aria-label'?: string; 'aria-label'?: string;
@ -81,6 +81,7 @@ const Button: FC<ButtonLinkProps> = ({
title={title} title={title}
data-testid={dataTestId} data-testid={dataTestId}
className={buttonClassNames} className={buttonClassNames}
onClick={otherProps.onClick}
style={style} style={style}
> >
{content} {content}
@ -96,6 +97,7 @@ const Button: FC<ButtonLinkProps> = ({
title={title} title={title}
data-testid={dataTestId} data-testid={dataTestId}
className={buttonClassNames} className={buttonClassNames}
onClick={otherProps.onClick}
style={style} style={style}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"

View File

@ -1,6 +1,7 @@
import { Link as LinkIcon } from '@styled-icons/material/Link'; import { Link as LinkIcon } from '@styled-icons/material/Link';
import { import {
ELEMENT_LINK, ELEMENT_LINK,
deleteText,
getEditorString, getEditorString,
getNode, getNode,
getSelectionText, getSelectionText,
@ -9,7 +10,7 @@ import {
setNodes, setNodes,
someNode, someNode,
} from '@udecode/plate'; } from '@udecode/plate';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo } from 'react';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import useUUID from '@staticcms/core/lib/hooks/useUUID'; import useUUID from '@staticcms/core/lib/hooks/useUUID';
@ -21,7 +22,7 @@ import type { Collection, MarkdownField, MediaPath } from '@staticcms/core/inter
import type { MdLinkElement } from '@staticcms/markdown/plate/plateTypes'; import type { MdLinkElement } from '@staticcms/markdown/plate/plateTypes';
import type { TText } from '@udecode/plate'; import type { TText } from '@udecode/plate';
import type { FC } from 'react'; import type { FC } from 'react';
import type { Path } from 'slate'; import type { Location } from 'slate';
export interface InsertLinkToolbarButtonProps { export interface InsertLinkToolbarButtonProps {
variant: 'button' | 'menu'; variant: 'button' | 'menu';
@ -38,25 +39,25 @@ const InsertLinkToolbarButton: FC<InsertLinkToolbarButtonProps> = ({
currentValue, currentValue,
disabled, disabled,
}) => { }) => {
const [selection, setSelection] = useState<Path>();
const editor = useMdPlateEditorState(); const editor = useMdPlateEditorState();
const handleInsert = useCallback( const handleInsert = useCallback(
({ path: newUrl, alt: newText }: MediaPath<string>) => { ({ path: newUrl, alt: newText }: MediaPath<string>) => {
if (isNotEmpty(newUrl) && selection) { const selectionPoint = editor.selection?.focus.path;
if (isNotEmpty(newUrl) && selectionPoint) {
const text = isNotEmpty(newText) ? newText : newUrl; const text = isNotEmpty(newText) ? newText : newUrl;
const linkAt = getNode<MdLinkElement>(editor, selection); const linkAt = getNode<MdLinkElement>(editor, selectionPoint);
if (linkAt && linkAt.type === ELEMENT_LINK) { if (linkAt && linkAt.type === ELEMENT_LINK) {
if (newUrl !== linkAt.url || text !== linkAt.children[0].text) { if (newUrl !== linkAt.url || text !== linkAt.children[0].text) {
setNodes<MdLinkElement>( setNodes<MdLinkElement>(
editor, editor,
{ url: newUrl, children: [{ text: newText }] }, { url: newUrl, children: [{ text: newText }] },
{ at: selection }, { at: selectionPoint },
); );
if (text !== getEditorString(editor, selection)) { if (text !== getEditorString(editor, selectionPoint)) {
replaceNodeChildren<TText>(editor, { replaceNodeChildren<TText>(editor, {
at: selection, at: selectionPoint,
nodes: { text }, nodes: { text },
insertOptions: { insertOptions: {
select: true, select: true,
@ -68,32 +69,29 @@ const InsertLinkToolbarButton: FC<InsertLinkToolbarButtonProps> = ({
return; return;
} }
console.log('editor.selection', editor.selection);
deleteText(editor, {
at: editor.selection as unknown as Location,
});
insertLink( insertLink(
editor, editor,
{ url: newUrl, text }, { url: newUrl, text },
{ {
at: selection, at: editor.selection as unknown as Location,
}, },
); );
const newSelection = [...selection];
const lastIndex = newSelection.pop() ?? 0;
setSelection([...newSelection, lastIndex + 1]);
} }
}, },
[editor, selection], [editor],
); );
const chooseUrl = useMemo(() => field.choose_url ?? true, [field.choose_url]); const chooseUrl = useMemo(() => field.choose_url ?? true, [field.choose_url]);
const isLink = !!editor?.selection && someNode(editor, { match: { type: ELEMENT_LINK } }); const isLink = !!editor?.selection && someNode(editor, { match: { type: ELEMENT_LINK } });
const selectedText: string = useMemo(() => { const selectedText = !editor.selection ? '' : getSelectionText(editor);
if (!editor.selection) {
return '';
}
return getSelectionText(editor);
}, [editor]);
const controlID = useUUID(); const controlID = useUUID();
const openMediaLibrary = useMediaInsert( const openMediaLibrary = useMediaInsert(
@ -106,21 +104,19 @@ const InsertLinkToolbarButton: FC<InsertLinkToolbarButtonProps> = ({
); );
const handleOpenMediaLibrary = useCallback(() => { const handleOpenMediaLibrary = useCallback(() => {
setSelection(editor.selection?.focus.path);
openMediaLibrary(); openMediaLibrary();
}, [editor.selection, openMediaLibrary]); }, [openMediaLibrary]);
return ( return !isLink ? (
<ToolbarButton <ToolbarButton
label="Link" label="Link"
tooltip="Insert link" tooltip="Insert link"
icon={LinkIcon} icon={LinkIcon}
onClick={handleOpenMediaLibrary} onClick={handleOpenMediaLibrary}
active={isLink}
disabled={disabled} disabled={disabled}
variant={variant} variant={variant}
/> />
); ) : null;
}; };
export default InsertLinkToolbarButton; export default InsertLinkToolbarButton;

View File

@ -13,6 +13,7 @@ import type {
MarkdownField, MarkdownField,
MediaPath, MediaPath,
} from '@staticcms/core/interface'; } from '@staticcms/core/interface';
import type { MouseEvent } from 'react';
export interface MediaPopoverProps<T extends FileOrImageField | MarkdownField> { export interface MediaPopoverProps<T extends FileOrImageField | MarkdownField> {
anchorEl: HTMLElement | null; anchorEl: HTMLElement | null;
@ -69,6 +70,10 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
handleMediaChange, handleMediaChange,
); );
const noop = useCallback((event: MouseEvent) => {
event.stopPropagation();
}, []);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const id = open ? 'edit-popover' : undefined; const id = open ? 'edit-popover' : undefined;
@ -122,7 +127,7 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
" "
/> />
{!forImage ? ( {!forImage ? (
<Button href={url} variant="text" size="small"> <Button href={url} variant="text" size="small" onClick={noop}>
<OpenInNewIcon className="w-4 h-4" title="Open In New Tab" /> <OpenInNewIcon className="w-4 h-4" title="Open In New Tab" />
</Button> </Button>
) : null} ) : null}