import { isAccelKey, preventDefault, TiptapEditor, useEditor } from '@tldraw/editor' import { useEffect, useMemo, useState } from 'react' import { useUiEvents } from '../../context/events' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon' import { TldrawUiToolbarButton } from '../primitives/TldrawUiToolbar' /** @public */ export interface DefaultRichTextToolbarContentProps { onEditLinkStart?(): void textEditor: TiptapEditor } /** * Rich text toolbar items that have the basics. * * @public @react */ export function DefaultRichTextToolbarContent({ textEditor, onEditLinkStart, }: DefaultRichTextToolbarContentProps) { const editor = useEditor() const trackEvent = useUiEvents() const msg = useTranslation() const source = 'rich-text-menu' // We need to force this one to update when the editor updates or when selection changes const [_, set] = useState(0) useEffect( function forceUpdateWhenContentChanges() { function forceUpdate() { set((t) => t + 1) } textEditor.on('update', forceUpdate) textEditor.on('selectionUpdate', forceUpdate) }, [textEditor] ) useEffect(() => { function handleKeyDown(event: KeyboardEvent) { if (onEditLinkStart && isAccelKey(event) && event.shiftKey && event.key === 'k') { event.preventDefault() onEditLinkStart() } } const doc = editor.getContainerDocument() doc.addEventListener('keydown', handleKeyDown) return () => { doc.removeEventListener('keydown', handleKeyDown) } }, [editor, onEditLinkStart]) // todo: we could make this a prop const actions = useMemo(() => { function handleOp(name: string, op: string) { // Check if the editor view is available before calling operations if (!textEditor.view) return trackEvent('rich-text', { operation: name as any, source }) // @ts-expect-error typing this is annoying at the moment. textEditor.chain().focus()[op]().run() } return [ // { name: 'heading', attrs: { level: 3 }, onSelect() { textEditor.chain().focus().toggleHeading({ level: 3}).run() }}, { name: 'bold', onSelect() { handleOp('bold', 'toggleBold') }, }, { name: 'italic', onSelect() { handleOp('bold', 'toggleItalic') }, }, // { name: 'underline', onSelect() { handleOp('underline', 'toggleUnderline') }}, // { name: 'strike', onSelect() { handleOp('strike', 'toggleStrike') }}, { name: 'code', onSelect() { handleOp('bold', 'toggleCode') }, }, onEditLinkStart ? { name: 'link', onSelect() { onEditLinkStart() }, } : undefined, // ? is this really optional? { name: 'bulletList', onSelect() { handleOp('bulletList', 'toggleBulletList') }, }, { name: 'highlight', onSelect() { handleOp('bulletList', 'toggleHighlight') }, }, ].filter(Boolean) as { name: string attrs?: string onSelect(): void }[] }, [textEditor, trackEvent, onEditLinkStart]) return actions.map(({ name, attrs, onSelect }) => { const isActive = textEditor.view ? textEditor.isActive(name, attrs) : false return ( ) }) }