fix(richtext-lexical): floating link editor did not properly hide if selection is not a range selection

This commit is contained in:
Alessio Gravili
2024-05-06 14:50:52 -04:00
parent 9f37bf7397
commit 20455f4fc2

View File

@@ -64,89 +64,101 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
depth: editDepth,
})
const setNotLink = useCallback(() => {
setIsLink(false)
if (editorRef && editorRef.current) {
editorRef.current.style.opacity = '0'
editorRef.current.style.transform = 'translate(-10000px, -10000px)'
}
setIsAutoLink(false)
setLinkUrl(null)
setLinkLabel(null)
setSelectedNodes([])
}, [setIsLink, setLinkUrl, setLinkLabel, setSelectedNodes])
const updateLinkEditor = useCallback(() => {
const selection = $getSelection()
let selectedNodeDomRect: DOMRect | undefined = null
if (!$isRangeSelection(selection) || !selection) {
setNotLink()
return
}
// Handle the data displayed in the floating link editor & drawer when you click on a link node
if ($isRangeSelection(selection)) {
const focusNode = getSelectedNode(selection)
selectedNodeDomRect = editor.getElementByKey(focusNode.getKey())?.getBoundingClientRect()
const focusLinkParent: LinkNode = $findMatchingParent(focusNode, $isLinkNode)
// Prevent link modal from showing if selection spans further than the link: https://github.com/facebook/lexical/issues/4064
const badNode = selection
.getNodes()
.filter((node) => !$isLineBreakNode(node))
.find((node) => {
const linkNode = $findMatchingParent(node, $isLinkNode)
return (
(focusLinkParent && !focusLinkParent.is(linkNode)) ||
(linkNode && !linkNode.is(focusLinkParent))
)
})
const focusNode = getSelectedNode(selection)
selectedNodeDomRect = editor.getElementByKey(focusNode.getKey())?.getBoundingClientRect()
const focusLinkParent: LinkNode = $findMatchingParent(focusNode, $isLinkNode)
if (focusLinkParent == null || badNode) {
setIsLink(false)
setIsAutoLink(false)
setLinkUrl(null)
setLinkLabel(null)
setSelectedNodes([])
return
}
// Prevent link modal from showing if selection spans further than the link: https://github.com/facebook/lexical/issues/4064
const badNode = selection
.getNodes()
.filter((node) => !$isLineBreakNode(node))
.find((node) => {
const linkNode = $findMatchingParent(node, $isLinkNode)
return (
(focusLinkParent && !focusLinkParent.is(linkNode)) ||
(linkNode && !linkNode.is(focusLinkParent))
)
})
// Initial state:
const data: LinkFields & { text: string } = {
doc: undefined,
linkType: undefined,
newTab: undefined,
url: '',
...focusLinkParent.getFields(),
text: focusLinkParent.getTextContent(),
}
if (focusLinkParent == null || badNode) {
setNotLink()
return
}
if (focusLinkParent.getFields()?.linkType === 'custom') {
setLinkUrl(focusLinkParent.getFields()?.url ?? null)
setLinkLabel(null)
} else {
// internal link
// Initial state:
const data: LinkFields & { text: string } = {
doc: undefined,
linkType: undefined,
newTab: undefined,
url: '',
...focusLinkParent.getFields(),
text: focusLinkParent.getTextContent(),
}
if (focusLinkParent.getFields()?.linkType === 'custom') {
setLinkUrl(focusLinkParent.getFields()?.url ?? null)
setLinkLabel(null)
} else {
// internal link
setLinkUrl(
`/admin/collections/${focusLinkParent.getFields()?.doc?.relationTo}/${
focusLinkParent.getFields()?.doc?.value
}`,
)
const relatedField = config.collections.find(
(coll) => coll.slug === focusLinkParent.getFields()?.doc?.relationTo,
)
if (!relatedField) {
// Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all.
// label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links.
setLinkLabel(
focusLinkParent.getFields()?.label ? String(focusLinkParent.getFields()?.label) : null,
)
setLinkUrl(
`/admin/collections/${focusLinkParent.getFields()?.doc?.relationTo}/${
focusLinkParent.getFields()?.doc?.value
}`,
focusLinkParent.getFields()?.url ? String(focusLinkParent.getFields()?.url) : null,
)
const relatedField = config.collections.find(
(coll) => coll.slug === focusLinkParent.getFields()?.doc?.relationTo,
)
if (!relatedField) {
// Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all.
// label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links.
setLinkLabel(
focusLinkParent.getFields()?.label ? String(focusLinkParent.getFields()?.label) : null,
)
setLinkUrl(
focusLinkParent.getFields()?.url ? String(focusLinkParent.getFields()?.url) : null,
)
} else {
const label = t('fields:linkedTo', {
label: getTranslation(relatedField.labels.singular, i18n),
}).replace(/<[^>]*>?/g, '')
setLinkLabel(label)
}
}
setStateData(data)
setIsLink(true)
setSelectedNodes(selection ? selection?.getNodes() : [])
if ($isAutoLinkNode(focusLinkParent)) {
setIsAutoLink(true)
} else {
setIsAutoLink(false)
const label = t('fields:linkedTo', {
label: getTranslation(relatedField.labels.singular, i18n),
}).replace(/<[^>]*>?/g, '')
setLinkLabel(label)
}
}
setStateData(data)
setIsLink(true)
setSelectedNodes(selection ? selection?.getNodes() : [])
if ($isAutoLinkNode(focusLinkParent)) {
setIsAutoLink(true)
} else {
setIsAutoLink(false)
}
const editorElem = editorRef.current
const nativeSelection = window.getSelection()
const { activeElement } = document
@@ -158,7 +170,6 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
const rootElement = editor.getRootElement()
if (
selection !== null &&
nativeSelection !== null &&
rootElement !== null &&
rootElement.contains(nativeSelection.anchorNode)
@@ -182,7 +193,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
}
return true
}, [anchorElem, editor, config, t, i18n])
}, [editor, setNotLink, config.collections, t, i18n, anchorElem])
useEffect(() => {
return mergeRegister(
@@ -202,13 +213,6 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
)
}, [editor, updateLinkEditor, toggleModal, drawerSlug])
useEffect(() => {
if (!isLink && editorRef) {
editorRef.current.style.opacity = '0'
editorRef.current.style.transform = 'translate(-10000px, -10000px)'
}
}, [isLink])
useEffect(() => {
const scrollerElem = anchorElem.parentElement
@@ -253,8 +257,8 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
KEY_ESCAPE_COMMAND,
() => {
if (isLink) {
setIsLink(false)
setIsAutoLink(false)
setNotLink()
return true
}
return false
@@ -262,7 +266,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
COMMAND_PRIORITY_HIGH,
),
)
}, [editor, updateLinkEditor, setIsLink, isLink])
}, [editor, updateLinkEditor, isLink, setNotLink])
useEffect(() => {
editor.getEditorState().read(() => {