import React from 'react' import MDEditor from '@uiw/react-md-editor' import { BaseBoxShapeUtil, TLBaseShape } from '@tldraw/tldraw' export type IMarkdownShape = TLBaseShape< 'Markdown', { w: number h: number text: string } > export class MarkdownShape extends BaseBoxShapeUtil { static type = 'Markdown' as const getDefaultProps(): IMarkdownShape['props'] { return { w: 300, h: 200, text: '', } } component(shape: IMarkdownShape) { // Hooks must be at the top level const isSelected = this.editor.getSelectedShapeIds().includes(shape.id) const markdownRef = React.useRef(null) // Single useEffect hook that handles checkbox interactivity React.useEffect(() => { if (!isSelected && markdownRef.current) { const checkboxes = markdownRef.current.querySelectorAll('input[type="checkbox"]') checkboxes.forEach((checkbox) => { checkbox.removeAttribute('disabled') checkbox.addEventListener('click', handleCheckboxClick) }) // Cleanup function return () => { if (markdownRef.current) { const checkboxes = markdownRef.current.querySelectorAll('input[type="checkbox"]') checkboxes.forEach((checkbox) => { checkbox.removeEventListener('click', handleCheckboxClick) }) } } } }, [isSelected, shape.props.text]) // Handler function defined outside useEffect const handleCheckboxClick = (event: Event) => { event.stopPropagation() const target = event.target as HTMLInputElement const checked = target.checked const text = shape.props.text const lines = text.split('\n') const checkboxRegex = /^\s*[-*+]\s+\[([ x])\]/ const newText = lines.map(line => { if (line.includes(target.parentElement?.textContent || '')) { return line.replace(checkboxRegex, `- [${checked ? 'x' : ' '}]`) } return line }).join('\n') this.editor.updateShape({ id: shape.id, type: 'Markdown', props: { ...shape.props, text: newText, }, }) } const wrapperStyle: React.CSSProperties = { width: '100%', height: '100%', backgroundColor: 'white', border: '1px solid #ddd', borderRadius: '4px', overflow: 'hidden', } // Simplified contentStyle - removed padding and center alignment const contentStyle: React.CSSProperties = { width: '100%', height: '100%', backgroundColor: '#FFFFFF', cursor: isSelected ? 'text' : 'default', pointerEvents: 'all', } // Show MDEditor when selected if (isSelected) { return (
{ this.editor.updateShape({ id: shape.id, type: 'Markdown', props: { ...shape.props, text: value, }, }) }} preview='edit' hideToolbar={true} style={{ height: '100%', border: 'none', }} textareaProps={{ placeholder: "Enter markdown text...", style: { backgroundColor: 'transparent', height: '100%', padding: '12px', lineHeight: '1.5', fontSize: '14px', } }} onPointerDown={(e) => { e.stopPropagation() }} />
) } // Show rendered markdown when not selected return (
{shape.props.text ? ( ) : ( Click to edit markdown... )}
) } indicator(shape: IMarkdownShape) { return } // Add handlers for better interaction override onDoubleClick = (shape: IMarkdownShape) => { const textarea = document.querySelector(`[data-shape-id="${shape.id}"] textarea`) as HTMLTextAreaElement textarea?.focus() } onPointerDown = (shape: IMarkdownShape) => { if (!shape.props.text) { const textarea = document.querySelector(`[data-shape-id="${shape.id}"] textarea`) as HTMLTextAreaElement textarea?.focus() } } }