canvas-website/src/shapes/MarkdownShapeUtil.tsx

203 lines
5.4 KiB
TypeScript

/** TODO: build this */
import { BaseBoxShapeUtil, TLBaseBoxShape, TLBaseShape, StyleProp, T, DefaultSizeStyle, DefaultFontStyle, DefaultColorStyle } from "tldraw"
import { useEffect, useRef, useState } from "react"
import { marked } from "marked"
// Uncomment and use these style definitions
const MarkdownColor = StyleProp.defineEnum('markdown:color', {
defaultValue: 'black',
values: ['black', 'blue', 'green', 'grey', 'light-blue', 'light-green', 'light-red', 'light-violet', 'orange', 'red', 'violet', 'yellow'],
})
const MarkdownSize = StyleProp.defineEnum('markdown:size', {
defaultValue: 'medium',
values: ['small', 'medium', 'large'],
})
const MarkdownFont = StyleProp.defineEnum('markdown:font', {
defaultValue: 'draw',
values: ['draw', 'sans', 'serif', 'mono'],
})
//const MarkdownHorizontalAlign = StyleProp.define('markdown:horizontalalign', { defaultValue: 'start' })
//const MarkdownVerticalAlign = StyleProp.define('markdown:verticalalign', { defaultValue: 'start' })
export type IMarkdownShape = TLBaseShape<
"MarkdownTool",
{
content: string
isPreview: boolean
w: number
h: number
color: string
size: string
font: string
}
>
export class MarkdownShape extends BaseBoxShapeUtil<
IMarkdownShape & TLBaseBoxShape
> {
static override type = "MarkdownTool"
static styles = {
color: DefaultColorStyle,
size: DefaultSizeStyle,
font: DefaultFontStyle,
} as any // Type assertion to allow dynamic property addition
styles = MarkdownShape.styles
constructor(props: any) {
super(props)
console.log('MarkdownShape constructor - styles:', this.styles)
// Add a fallback get method if it doesn't exist
if (!this.styles.get) {
this.styles.get = function(style: string) {
console.log('Fallback get called for style:', style)
return this[style] || null
}
}
}
getDefaultProps(): IMarkdownShape["props"] & { w: number; h: number } {
console.log('getDefaultProps called');
const props = {
content: "",
isPreview: false,
w: 400,
h: 300,
color: 'black',
size: 'medium',
font: 'draw'
};
console.log('Default props:', props);
return props;
}
indicator(shape: IMarkdownShape) {
return (
<g>
<rect x={0} y={0} width={shape.props.w} height={shape.props.h} />
</g>
)
}
component(shape: IMarkdownShape) {
console.log('Component rendering with shape:', shape);
console.log('Available styles:', this.styles);
const editor = this.editor
return <MarkdownEditor shape={shape} editor={editor} />
}
}
function MarkdownEditor({ shape, editor }: { shape: IMarkdownShape; editor: any }) {
console.log('MarkdownEditor mounted with shape:', shape);
console.log('Editor instance:', editor);
const textareaRef = useRef<HTMLTextAreaElement>(null)
const [isPreview, setIsPreview] = useState(shape.props.isPreview)
const [renderedContent, setRenderedContent] = useState("")
useEffect(() => {
if (textareaRef.current && textareaRef.current.value !== shape.props.content) {
textareaRef.current.value = shape.props.content
}
}, [shape.props.content])
useEffect(() => {
const html = marked.parse(shape.props.content, { breaks: true }) as string
setRenderedContent(html)
}, [shape.props.content])
const togglePreview = () => {
const newPreviewState = !isPreview
setIsPreview(newPreviewState)
editor.updateShape(shape.id, {
props: {
...shape.props,
isPreview: newPreviewState
}
})
}
return (
<div style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: 'white',
border: '1px solid #e0e0e0',
borderRadius: '4px',
overflow: 'hidden'
}}>
{/* Toolbar */}
<div style={{
padding: '4px 8px',
borderBottom: '1px solid #e0e0e0',
backgroundColor: '#f5f5f5',
display: 'flex',
gap: '8px',
alignItems: 'center'
}}>
<button
onClick={togglePreview}
style={{
padding: '4px 8px',
border: '1px solid #ccc',
borderRadius: '4px',
backgroundColor: 'white',
cursor: 'pointer',
fontSize: '12px'
}}
>
{isPreview ? 'Edit' : 'Preview'}
</button>
</div>
{/* Editor/Preview Area */}
<div style={{
flex: 1,
overflow: 'auto',
position: 'relative'
}}>
{isPreview ? (
<div
style={{
padding: '8px',
height: '100%',
overflow: 'auto'
}}
dangerouslySetInnerHTML={{
__html: marked(shape.props.content, { breaks: true }) as string
}}
/>
) : (
<textarea
ref={textareaRef}
defaultValue={shape.props.content}
style={{
width: '100%',
height: '100%',
resize: 'none',
border: 'none',
padding: '8px',
fontFamily: 'inherit',
}}
onChange={(e) => {
editor.updateShape(shape.id, {
props: {
...shape.props,
content: e.target.value
}
})
}}
/>
)}
</div>
</div>
)
}