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",
"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",

View File

@ -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;
}

View File

@ -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: "<h1>New Note</h1>",
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<HTMLTextAreaElement>) => {
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 (
<div
style={{
width: "100%",
height: "100%",
position: "relative",
background: "white",
borderRadius: "4px",
padding: "8px",
overflow: "auto",
cursor: "pointer",
}}
onDoubleClick={toggleEdit}
>
<MdEditor
style={{ height: "100%" }}
renderHTML={(text) => 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 ? (
<textarea
value={shape.props.content}
onChange={handleChange}
style={{
width: "100%",
height: "100%",
border: "none",
outline: "none",
resize: "none",
fontFamily: "inherit",
fontSize: "inherit",
backgroundColor: "transparent",
}}
autoFocus
onBlur={toggleEdit}
/>
) : (
<ReactMarkdown>{shape.props.content}</ReactMarkdown>
)}
</div>
)
}

View File

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

View File

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

View File

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