markdown editor failing at sync

This commit is contained in:
Jeff Emmett 2024-12-08 04:52:14 -05:00
parent 6f4dcb1bcb
commit 2fc965da28
6 changed files with 143 additions and 60 deletions

View File

@ -15,21 +15,23 @@
"author": "Jeff Emmett", "author": "Jeff Emmett",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@hackmd/markdown-it": "^12.0.23",
"@hackmd/markdown-it-task-lists": "^2.1.4",
"@tldraw/assets": "^3.6.0", "@tldraw/assets": "^3.6.0",
"@tldraw/sync": "^3.6.0", "@tldraw/sync": "^3.6.0",
"@tldraw/sync-core": "^3.6.0", "@tldraw/sync-core": "^3.6.0",
"@tldraw/tldraw": "^3.6.0", "@tldraw/tldraw": "^3.6.0",
"@tldraw/tlschema": "^3.6.0", "@tldraw/tlschema": "^3.6.0",
"@types/markdown-it": "^14.1.1",
"@vercel/analytics": "^1.2.2", "@vercel/analytics": "^1.2.2",
"cloudflare-workers-unfurl": "^0.0.7", "cloudflare-workers-unfurl": "^0.0.7",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"itty-router": "^5.0.17", "itty-router": "^5.0.17",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"markdown-it-task-lists": "^2.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^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", "react-router-dom": "^7.0.2",
"tldraw": "^3.6.0", "tldraw": "^3.6.0",
"vercel": "^39.1.1" "vercel": "^39.1.1"
@ -38,8 +40,10 @@
"@cloudflare/types": "^6.0.0", "@cloudflare/types": "^6.0.0",
"@cloudflare/workers-types": "^4.20240821.1", "@cloudflare/workers-types": "^4.20240821.1",
"@types/lodash.throttle": "^4", "@types/lodash.throttle": "^4",
"@types/markdown-it": "^14.1.2",
"@types/react": "^19.0.1", "@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1", "@types/react-dom": "^19.0.1",
"@types/styled-components": "^5.1.34",
"@vitejs/plugin-react": "^4.0.3", "@vitejs/plugin-react": "^4.0.3",
"@vitejs/plugin-react-swc": "^3.6.0", "@vitejs/plugin-react-swc": "^3.6.0",
"concurrently": "^9.1.0", "concurrently": "^9.1.0",

View File

@ -326,11 +326,10 @@ p:has(+ ol) {
background-color: white; background-color: white;
} }
.rc-md-editor {
border: none !important; .mdx-editor {
height: 100%;
padding: 8px;
outline: none;
font-family: inherit; font-family: inherit;
} }
.rc-md-editor .editor-container {
border-radius: 4px;
}

View File

@ -1,83 +1,163 @@
/** TODO: build this */
import { BaseBoxShapeUtil, TLBaseBoxShape, TLBaseShape } from "tldraw" import { BaseBoxShapeUtil, TLBaseBoxShape, TLBaseShape } from "tldraw"
import MdEditor from "react-markdown-editor-lite" import ReactMarkdown from "react-markdown"
import MarkdownIt from "markdown-it" import { useCallback, useState, useEffect } from "react"
import "react-markdown-editor-lite/lib/index.css"
// Initialize markdown parser
const mdParser = new MarkdownIt()
export type IMarkdownShape = TLBaseShape< export type IMarkdownShape = TLBaseShape<
"MarkdownTool", "markdown",
{ {
content: string content: string
html: string w: number
h: number
isEditing: boolean
fill: string
color: string
} }
> >
export class MarkdownShape extends BaseBoxShapeUtil< export class MarkdownShape extends BaseBoxShapeUtil<
IMarkdownShape & TLBaseBoxShape IMarkdownShape & TLBaseBoxShape
> { > {
static override type = "MarkdownTool" static override type = "Markdown"
static override props = {
indicator(_shape: IMarkdownShape) { content: {
return null 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 { return {
content: "# New Note",
html: "<h1>New Note</h1>",
w: 400, w: 400,
h: 300, 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) { component(shape: IMarkdownShape & TLBaseBoxShape) {
const handleEditorChange = ({ const [isEditing, setIsEditing] = useState(shape.props.isEditing)
text,
html, const handleChange = useCallback(
}: { (e: React.ChangeEvent<HTMLTextAreaElement>) => {
text: string this.editor?.updateShape({
html: string id: shape.id,
}) => { type: "markdown",
// Update the shape's content props: {
...shape.props,
content: e.target.value,
},
})
},
[shape.id],
)
const toggleEdit = useCallback(() => {
setIsEditing(!isEditing)
this.editor?.updateShape({ this.editor?.updateShape({
id: shape.id, id: shape.id,
type: "MarkdownTool", type: "markdown",
props: { props: {
...shape.props, ...shape.props,
content: text, isEditing: !isEditing,
html: html,
}, },
}) })
} }, [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 ( return (
<div <div
style={{ style={{
width: "100%", width: "100%",
height: "100%", height: "100%",
position: "relative",
background: "white", background: "white",
borderRadius: "4px", borderRadius: "4px",
padding: "8px",
overflow: "auto",
cursor: "pointer",
}} }}
onDoubleClick={toggleEdit}
> >
<MdEditor {isEditing ? (
style={{ height: "100%" }} <textarea
renderHTML={(text) => mdParser.render(text)} value={shape.props.content}
value={shape.props.content} onChange={handleChange}
onChange={handleEditorChange} style={{
view={{ menu: true, md: true, html: false }} width: "100%",
canView={{ height: "100%",
menu: true, border: "none",
md: true, outline: "none",
html: false, resize: "none",
both: false, fontFamily: "inherit",
fullScreen: false, fontSize: "inherit",
hideMenu: false, backgroundColor: "transparent",
}} }}
/> autoFocus
onBlur={toggleEdit}
/>
) : (
<ReactMarkdown>{shape.props.content}</ReactMarkdown>
)}
</div> </div>
) )
} }

View File

@ -1,7 +1,7 @@
import { BaseBoxShapeTool } from "tldraw" import { BaseBoxShapeTool } from "tldraw"
export class MarkdownTool extends BaseBoxShapeTool { export class MarkdownTool extends BaseBoxShapeTool {
static override id = "MarkdownTool" static override id = "markdown"
shapeType = "MarkdownTool" shapeType = "markdown"
override initial = "idle" override initial = "idle"
} }

View File

@ -99,12 +99,12 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
/> />
<TldrawUiMenuItem <TldrawUiMenuItem
id="markdown" id="markdown"
label="Create Markdown" label="Create Markdown Box"
icon="markdown" icon="markdown"
kbd="alt+m" kbd="alt+m"
disabled={hasSelection} disabled={hasSelection}
onSelect={() => { onSelect={() => {
editor.setCurrentTool("Markdown") editor.setCurrentTool("markdown")
}} }}
/> />
</TldrawUiMenuGroup> </TldrawUiMenuGroup>

View File

@ -1,5 +1,5 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react"
export default defineConfig({ export default defineConfig({
envPrefix: ["VITE_"], envPrefix: ["VITE_"],
@ -20,7 +20,7 @@ export default defineConfig({
}, },
define: { define: {
"import.meta.env.VITE_WORKER_URL": JSON.stringify( "import.meta.env.VITE_WORKER_URL": JSON.stringify(
process.env.VITE_WORKER_URL process.env.VITE_WORKER_URL,
), ),
}, },
}); })