import React, { useState, useCallback, useRef, useEffect } from 'react' import { MDXEditor, headingsPlugin, listsPlugin, quotePlugin, thematicBreakPlugin, markdownShortcutPlugin, linkPlugin, linkDialogPlugin, imagePlugin, tablePlugin, codeBlockPlugin, codeMirrorPlugin, diffSourcePlugin, toolbarPlugin, BoldItalicUnderlineToggles, UndoRedo, BlockTypeSelect, CreateLink, InsertTable, ListsToggle, Separator, DiffSourceToggleWrapper, type MDXEditorMethods, } from '@mdxeditor/editor' import '@mdxeditor/editor/style.css' import { BaseBoxShapeUtil, TLBaseShape, HTMLContainer } from '@tldraw/tldraw' import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper' import { usePinnedToView } from '../hooks/usePinnedToView' import { useMaximize } from '../hooks/useMaximize' export type IMarkdownShape = TLBaseShape< 'Markdown', { w: number h: number text: string pinnedToView: boolean tags: string[] } > export class MarkdownShape extends BaseBoxShapeUtil { static type = 'Markdown' as const // Markdown theme color: Teal static readonly PRIMARY_COLOR = "#14b8a6" getDefaultProps(): IMarkdownShape['props'] { return { w: 650, h: 400, text: '', pinnedToView: false, tags: ['markdown'], } } component(shape: IMarkdownShape) { const isSelected = this.editor.getSelectedShapeIds().includes(shape.id) const [isMinimized, setIsMinimized] = useState(false) const [isToolbarMinimized, setIsToolbarMinimized] = useState(false) const editorRef = useRef(null) // Dark mode detection with reactive updates const [isDarkMode, setIsDarkMode] = useState(() => { if (typeof document !== 'undefined') { return document.documentElement.classList.contains('dark') } return false }) // Listen for dark mode changes useEffect(() => { if (typeof document === 'undefined') return const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'class') { setIsDarkMode(document.documentElement.classList.contains('dark')) } }) }) observer.observe(document.documentElement, { attributes: true }) return () => observer.disconnect() }, []) // Use the pinning hook usePinnedToView(this.editor, shape.id, shape.props.pinnedToView) // Use the maximize hook for fullscreen functionality const { isMaximized, toggleMaximize } = useMaximize({ editor: this.editor, shapeId: shape.id, currentW: shape.props.w, currentH: shape.props.h, shapeType: 'Markdown', }) const handleClose = () => { this.editor.deleteShape(shape.id) } const handleMinimize = () => { setIsMinimized(!isMinimized) } const handlePinToggle = () => { this.editor.updateShape({ id: shape.id, type: shape.type, props: { ...shape.props, pinnedToView: !shape.props.pinnedToView, }, }) } const handleChange = useCallback((newText: string) => { this.editor.updateShape({ id: shape.id, type: 'Markdown', props: { ...shape.props, text: newText, }, }) }, [shape.id, shape.props]) // Sync external changes to editor useEffect(() => { if (editorRef.current) { const currentMarkdown = editorRef.current.getMarkdown() if (currentMarkdown !== shape.props.text) { editorRef.current.setMarkdown(shape.props.text || '') } } }, [shape.props.text]) return ( { this.editor.updateShape({ id: shape.id, type: 'Markdown', props: { ...shape.props, tags: newTags, } }) }} tagsEditable={true} >
e.stopPropagation()} onWheel={(e) => e.stopPropagation()} > { // Return a placeholder - can be extended to support actual uploads return Promise.resolve('https://via.placeholder.com/400x300') }, }), // Markdown shortcuts (type # for heading, * for list, etc.) markdownShortcutPlugin(), // Source mode toggle (rich-text vs raw markdown) diffSourcePlugin({ viewMode: 'rich-text', diffMarkdown: shape.props.text || '', }), // Toolbar toolbarPlugin({ toolbarContents: () => ( isToolbarMinimized ? ( // When minimized, show nothing - the toggle button is rendered outside <> ) : (
<>
) ) }), ]} /> {/* Toolbar toggle button - fixed position, doesn't move */}
{/* Custom styles for the MDXEditor */}
) } indicator(shape: IMarkdownShape) { return } override onDoubleClick = (shape: IMarkdownShape) => { // Focus the editor on double-click const editorElement = document.querySelector(`[data-shape-id="${shape.id}"] .mdxeditor [contenteditable="true"]`) as HTMLElement editorElement?.focus() } }