fix: insert link for selected text behavior (#858)
This commit is contained in:
committed by
GitHub
parent
1fee80046f
commit
316c8b62a3
@ -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"
|
||||||
|
@ -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;
|
||||||
|
@ -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}
|
||||||
|
Reference in New Issue
Block a user