From 9a052646fd0a1c957d60bfc3ee6291c9ed86f6a5 Mon Sep 17 00:00:00 2001 From: Petyo Ivanov Date: Mon, 13 Nov 2023 11:50:50 +0200 Subject: [PATCH] feat: link dialog works without selection Fixes #175 --- src/examples/site-demo.tsx | 2 +- src/plugins/core/ui/DownshiftAutoComplete.tsx | 25 ++++++- src/plugins/image/ImageDialog.tsx | 1 + src/plugins/link-dialog/LinkDialog.tsx | 1 + src/plugins/link-dialog/index.ts | 73 ++++++++++++------- src/utils/lexicalHelpers.ts | 25 +++++-- 6 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/examples/site-demo.tsx b/src/examples/site-demo.tsx index 1406cc27..54002ae1 100644 --- a/src/examples/site-demo.tsx +++ b/src/examples/site-demo.tsx @@ -4,5 +4,5 @@ import markdown from './assets/live-demo-contents.md?raw' import { ALL_PLUGINS } from './_boilerplate' export function Basics() { - return console.log(md)} markdown={markdown} plugins={ALL_PLUGINS} /> + return } diff --git a/src/plugins/core/ui/DownshiftAutoComplete.tsx b/src/plugins/core/ui/DownshiftAutoComplete.tsx index dbfa4869..3e7b2897 100644 --- a/src/plugins/core/ui/DownshiftAutoComplete.tsx +++ b/src/plugins/core/ui/DownshiftAutoComplete.tsx @@ -1,20 +1,39 @@ import { useCombobox } from 'downshift' import React from 'react' -import { Control, UseFormSetValue, Controller } from 'react-hook-form' +import { Control, UseFormSetValue, Controller, UseFormRegister } from 'react-hook-form' import styles from '../../../styles/ui.module.css' import DropDownIcon from '../../../icons/arrow_drop_down.svg' const MAX_SUGGESTIONS = 20 -export const DownshiftAutoComplete: React.FC<{ +interface DownshiftAutoCompleteProps { suggestions: string[] control: Control setValue: UseFormSetValue + register: UseFormRegister placeholder: string inputName: string autofocus?: boolean initialInputValue: string -}> = ({ autofocus, suggestions, control, inputName, placeholder, initialInputValue, setValue }) => { +} + +export const DownshiftAutoComplete: React.FC = (props) => { + if (props.suggestions.length > 0) { + return + } else { + return + } +} + +export const DownshiftAutoCompleteWithSuggestions: React.FC = ({ + autofocus, + suggestions, + control, + inputName, + placeholder, + initialInputValue, + setValue +}) => { const [items, setItems] = React.useState(suggestions.slice(0, MAX_SUGGESTIONS)) const enableAutoComplete = suggestions.length > 0 diff --git a/src/plugins/image/ImageDialog.tsx b/src/plugins/image/ImageDialog.tsx index c3db67bb..ab692f98 100644 --- a/src/plugins/image/ImageDialog.tsx +++ b/src/plugins/image/ImageDialog.tsx @@ -60,6 +60,7 @@ export const ImageDialog: React.FC = () => {
{ - const url = payload.url.trim() - const title = payload.title.trim() + r.sub( + r.pipe(updateLink, r.o.withLatestFrom(activeEditor, linkDialogState, currentSelection)), + ([payload, editor, state, selection]) => { + const url = payload.url.trim() + const title = payload.title.trim() + + if (url.trim() !== '') { + if (selection?.isCollapsed()) { + const linkContent = title || url + editor?.update( + () => { + if (!getLinkNodeInSelection(selection)) { + const node = $createTextNode(linkContent) + $insertNodes([node]) + node.select() + } + }, + { discrete: true } + ) + } + + editor?.dispatchCommand(TOGGLE_LINK_COMMAND, { url, title }) + + // the dispatch command implementation fails to set the title for a fresh link creation. + // Work around with the code below. + setTimeout(() => { + editor?.update(() => { + const node = getLinkNodeInSelection($getSelection() as RangeSelection) + node?.setTitle(title) + }) + }, 100) - if (url.trim() !== '') { - editor?.dispatchCommand(TOGGLE_LINK_COMMAND, { url, title }) - // the dispatch command implementation fails to set the title for a fresh link creation. - // Work around with the code below. - setTimeout(() => { - editor?.update(() => { - const node = getLinkNodeInSelection($getSelection() as RangeSelection) - node?.setTitle(title) + r.pub(linkDialogState, { + type: 'preview', + linkNodeKey: state.linkNodeKey, + rectangle: state.rectangle, + url + } as PreviewLinkDialog) + } else { + if (state.type === 'edit' && state.initialUrl !== '') { + editor?.dispatchCommand(TOGGLE_LINK_COMMAND, null) + } + r.pub(linkDialogState, { + type: 'inactive' }) - }) - r.pub(linkDialogState, { - type: 'preview', - linkNodeKey: state.linkNodeKey, - rectangle: state.rectangle, - url - } as PreviewLinkDialog) - } else { - if (state.type === 'edit' && state.initialUrl !== '') { - editor?.dispatchCommand(TOGGLE_LINK_COMMAND, null) } - r.pub(linkDialogState, { - type: 'inactive' - }) } - }) + ) r.link( r.pipe( diff --git a/src/utils/lexicalHelpers.ts b/src/utils/lexicalHelpers.ts index 52068f2e..664aef61 100644 --- a/src/utils/lexicalHelpers.ts +++ b/src/utils/lexicalHelpers.ts @@ -26,7 +26,7 @@ export function getSelectedNode(selection: RangeSelection): TextNode | ElementNo } else { return $isAtNodeEnd(anchor) ? anchorNode : focusNode } - } catch { + } catch (e) { return null } } @@ -47,16 +47,25 @@ export function getSelectionRectangle(editor: LexicalEditor) { ) { const domRange = nativeSelection.getRangeAt(0) let rect - if (nativeSelection.anchorNode === rootElement) { - let inner = rootElement - while (inner.firstElementChild != null) { - inner = inner.firstElementChild as HTMLElement + + if (nativeSelection.isCollapsed) { + let node = nativeSelection.anchorNode + if (node?.nodeType == 3) { + node = node.parentNode } - rect = inner.getBoundingClientRect() + rect = (node as HTMLElement).getBoundingClientRect() + rect.width = 0 } else { - rect = domRange.getBoundingClientRect() + if (nativeSelection.anchorNode === rootElement) { + let inner = rootElement + while (inner.firstElementChild != null) { + inner = inner.firstElementChild as HTMLElement + } + rect = inner.getBoundingClientRect() + } else { + rect = domRange.getBoundingClientRect() + } } - return { top: Math.round(rect.top), left: Math.round(rect.left),