import { openWindow, preventDefault, TiptapEditor, useEditor } from '@tldraw/editor' import { useCallback, useEffect, useRef, useState } from 'react' import { useUiEvents } from '../../context/events' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { TldrawUiButton } from '../primitives/Button/TldrawUiButton' import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon' import { TldrawUiInput } from '../primitives/TldrawUiInput' /** @public */ export interface LinkEditorProps { textEditor: TiptapEditor value: string onClose(): void } /** @public @react */ export function LinkEditor({ textEditor, value: initialValue, onClose }: LinkEditorProps) { const editor = useEditor() const [value, setValue] = useState(initialValue) const msg = useTranslation() const ref = useRef(null) const trackEvent = useUiEvents() const source = 'rich-text-menu' const linkifiedValue = value.startsWith('http') ? value : `https://${value}` const handleValueChange = (value: string) => setValue(value) const handleLinkComplete = useCallback( (link: string) => { trackEvent('rich-text', { operation: 'link-edit', source }) if (!link.startsWith('http://') && !link.startsWith('https://')) { link = `https://${link}` } textEditor.chain().setLink({ href: link }).run() // N.B. We shouldn't focus() on mobile because it causes the // Return key to replace the link with a newline :facepalm: if (editor.getInstanceState().isCoarsePointer) { textEditor.commands.blur() } else { textEditor.commands.focus() } onClose() }, [trackEvent, source, textEditor, editor, onClose] ) const handleVisitLink = () => { trackEvent('rich-text', { operation: 'link-visit', source }) openWindow(linkifiedValue, '_blank') onClose() } const handleRemoveLink = useCallback(() => { trackEvent('rich-text', { operation: 'link-remove', source }) textEditor.chain().unsetLink().focus().run() onClose() }, [trackEvent, source, textEditor, onClose]) const handleLinkCancel = () => onClose() useEffect(() => { const doc = editor.getContainerDocument() const handlePointerDown = (e: PointerEvent) => { const toolbar = doc.querySelector('.tlui-rich-text__toolbar') if (toolbar?.contains(e.target as Node)) return // If the pointer down is not in the toolbar, complete the link if (value) { handleLinkComplete(value) } else { handleRemoveLink() } } doc.addEventListener('pointerdown', handlePointerDown, { capture: true }) return () => { doc.removeEventListener('pointerdown', handlePointerDown, { capture: true }) } }, [editor, handleLinkComplete, handleRemoveLink, value]) useEffect(() => { ref.current?.focus() }, [value]) useEffect(() => { setValue(initialValue) }, [initialValue]) return ( <> ) }