diff --git a/package.json b/package.json index 83befd3..6866ea0 100644 --- a/package.json +++ b/package.json @@ -15,21 +15,23 @@ "author": "Jeff Emmett", "license": "ISC", "dependencies": { + "@hackmd/markdown-it": "^12.0.23", + "@hackmd/markdown-it-task-lists": "^2.1.4", "@tldraw/assets": "^3.6.0", "@tldraw/sync": "^3.6.0", "@tldraw/sync-core": "^3.6.0", "@tldraw/tldraw": "^3.6.0", "@tldraw/tlschema": "^3.6.0", - "@types/markdown-it": "^14.1.1", "@vercel/analytics": "^1.2.2", "cloudflare-workers-unfurl": "^0.0.7", "gray-matter": "^4.0.3", "itty-router": "^5.0.17", "lodash.throttle": "^4.1.1", "markdown-it": "^14.1.0", + "markdown-it-task-lists": "^2.1.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-markdown-editor-lite": "^1.3.4", + "react-markdown": "^9.0.1", "react-router-dom": "^7.0.2", "tldraw": "^3.6.0", "vercel": "^39.1.1" @@ -38,8 +40,10 @@ "@cloudflare/types": "^6.0.0", "@cloudflare/workers-types": "^4.20240821.1", "@types/lodash.throttle": "^4", + "@types/markdown-it": "^14.1.2", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.1", + "@types/styled-components": "^5.1.34", "@vitejs/plugin-react": "^4.0.3", "@vitejs/plugin-react-swc": "^3.6.0", "concurrently": "^9.1.0", diff --git a/src/css/style.css b/src/css/style.css index f87f64e..3fa08a2 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -326,11 +326,10 @@ p:has(+ ol) { background-color: white; } -.rc-md-editor { - border: none !important; - font-family: inherit; -} -.rc-md-editor .editor-container { - border-radius: 4px; +.mdx-editor { + height: 100%; + padding: 8px; + outline: none; + font-family: inherit; } \ No newline at end of file diff --git a/src/shapes/MarkdownShapeUtil.tsx b/src/shapes/MarkdownShapeUtil.tsx index aa27c19..5887e0e 100644 --- a/src/shapes/MarkdownShapeUtil.tsx +++ b/src/shapes/MarkdownShapeUtil.tsx @@ -1,83 +1,163 @@ -/** TODO: build this */ - import { BaseBoxShapeUtil, TLBaseBoxShape, TLBaseShape } from "tldraw" -import MdEditor from "react-markdown-editor-lite" -import MarkdownIt from "markdown-it" -import "react-markdown-editor-lite/lib/index.css" - -// Initialize markdown parser -const mdParser = new MarkdownIt() +import ReactMarkdown from "react-markdown" +import { useCallback, useState, useEffect } from "react" export type IMarkdownShape = TLBaseShape< - "MarkdownTool", + "markdown", { content: string - html: string + w: number + h: number + isEditing: boolean + fill: string + color: string } > export class MarkdownShape extends BaseBoxShapeUtil< IMarkdownShape & TLBaseBoxShape > { - static override type = "MarkdownTool" - - indicator(_shape: IMarkdownShape) { - return null + static override type = "Markdown" + static override props = { + content: { + type: "string", + value: "# New Note", + validate: (value: unknown) => typeof value === "string", + }, + w: { + type: "number", + value: 400, + validate: (value: unknown) => typeof value === "number", + }, + h: { + type: "number", + value: 300, + validate: (value: unknown) => typeof value === "number", + }, + isEditing: { + type: "boolean", + value: false, + validate: (value: unknown) => typeof value === "boolean", + }, + fill: { + type: "string", + value: "white", + validate: (value: unknown) => typeof value === "string", + }, + color: { + type: "string", + value: "black", + validate: (value: unknown) => typeof value === "string", + }, } - getDefaultProps(): IMarkdownShape["props"] & { w: number; h: number } { + override getDefaultProps(): IMarkdownShape["props"] & + TLBaseBoxShape["props"] { return { - content: "# New Note", - html: "

New Note

", w: 400, h: 300, + content: "# New Note", + isEditing: false, + fill: "white", + color: "black", + } + } + + getStyleProps(shape: IMarkdownShape) { + return { + fill: shape.props.fill, + color: shape.props.color, + } + } + + override indicator(shape: IMarkdownShape & TLBaseBoxShape) { + return { + x: shape.x, + y: shape.y, + width: shape.props.w, + height: shape.props.h, + rotation: shape.rotation, } } component(shape: IMarkdownShape & TLBaseBoxShape) { - const handleEditorChange = ({ - text, - html, - }: { - text: string - html: string - }) => { - // Update the shape's content + const [isEditing, setIsEditing] = useState(shape.props.isEditing) + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + this.editor?.updateShape({ + id: shape.id, + type: "markdown", + props: { + ...shape.props, + content: e.target.value, + }, + }) + }, + [shape.id], + ) + + const toggleEdit = useCallback(() => { + setIsEditing(!isEditing) this.editor?.updateShape({ id: shape.id, - type: "MarkdownTool", + type: "markdown", props: { ...shape.props, - content: text, - html: html, + isEditing: !isEditing, }, }) - } + }, [isEditing, shape.id, shape.props]) + + useEffect(() => { + return () => { + if (isEditing) { + this.editor?.updateShape({ + id: shape.id, + type: "markdown", + props: { + ...shape.props, + isEditing: false, + }, + }) + } + } + }, [shape.id, isEditing]) return (
- mdParser.render(text)} - value={shape.props.content} - onChange={handleEditorChange} - view={{ menu: true, md: true, html: false }} - canView={{ - menu: true, - md: true, - html: false, - both: false, - fullScreen: false, - hideMenu: false, - }} - /> + {isEditing ? ( +