Skip to content

Commit

Permalink
feat: link dialog works without selection
Browse files Browse the repository at this point in the history
Fixes #175
  • Loading branch information
petyosi committed Nov 13, 2023
1 parent e4d19ca commit 9a05264
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/examples/site-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import markdown from './assets/live-demo-contents.md?raw'
import { ALL_PLUGINS } from './_boilerplate'

export function Basics() {
return <MDXEditor onChange={(md) => console.log(md)} markdown={markdown} plugins={ALL_PLUGINS} />
return <MDXEditor markdown={markdown} plugins={ALL_PLUGINS} />
}
25 changes: 22 additions & 3 deletions src/plugins/core/ui/DownshiftAutoComplete.tsx
Original file line number Diff line number Diff line change
@@ -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<any, any>
setValue: UseFormSetValue<any>
register: UseFormRegister<any>
placeholder: string
inputName: string
autofocus?: boolean
initialInputValue: string
}> = ({ autofocus, suggestions, control, inputName, placeholder, initialInputValue, setValue }) => {
}

export const DownshiftAutoComplete: React.FC<DownshiftAutoCompleteProps> = (props) => {
if (props.suggestions.length > 0) {
return <DownshiftAutoCompleteWithSuggestions {...props} />
} else {
return <input className={styles.textInput} size={40} autoFocus {...props.register(props.inputName)} />
}
}

export const DownshiftAutoCompleteWithSuggestions: React.FC<DownshiftAutoCompleteProps> = ({
autofocus,
suggestions,
control,
inputName,
placeholder,
initialInputValue,
setValue
}) => {
const [items, setItems] = React.useState(suggestions.slice(0, MAX_SUGGESTIONS))

const enableAutoComplete = suggestions.length > 0
Expand Down
1 change: 1 addition & 0 deletions src/plugins/image/ImageDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const ImageDialog: React.FC = () => {
<div className={styles.formField}>
<label htmlFor="src">Or add an image from an URL:</label>
<DownshiftAutoComplete
register={register}
initialInputValue={state.type === 'editing' ? state.initialValues.src || '' : ''}
inputName="src"
suggestions={imageAutocompleteSuggestions}
Expand Down
1 change: 1 addition & 0 deletions src/plugins/link-dialog/LinkDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function LinkEditForm({ url, title, onSubmit, onCancel, linkAutocompleteS
<div className={styles.formField}>
<label htmlFor="link-url">URL</label>
<DownshiftAutoComplete
register={register}
initialInputValue={url}
inputName="url"
suggestions={linkAutocompleteSuggestions}
Expand Down
73 changes: 47 additions & 26 deletions src/plugins/link-dialog/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
import {
$createTextNode,
$getSelection,
$insertNodes,
$isRangeSelection,
COMMAND_PRIORITY_HIGH,
COMMAND_PRIORITY_LOW,
Expand Down Expand Up @@ -136,7 +138,7 @@ const linkDialogSystem = system(
const selection = $getSelection()
// we open the dialog if there's an actual selection
// or if the cursor is inside a link
if ($isRangeSelection(selection) && (getLinkNodeInSelection(selection) || !selection.isCollapsed())) {
if ($isRangeSelection(selection)) {
r.pub(openLinkEditDialog, true)
event.stopPropagation()
event.preventDefault()
Expand Down Expand Up @@ -172,35 +174,54 @@ const linkDialogSystem = system(
linkDialogState
)

r.sub(r.pipe(updateLink, r.o.withLatestFrom(activeEditor, linkDialogState)), ([payload, editor, state]) => {
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(
Expand Down
25 changes: 17 additions & 8 deletions src/utils/lexicalHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function getSelectedNode(selection: RangeSelection): TextNode | ElementNo
} else {
return $isAtNodeEnd(anchor) ? anchorNode : focusNode
}
} catch {
} catch (e) {
return null
}
}
Expand All @@ -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),
Expand Down

0 comments on commit 9a05264

Please sign in to comment.