removed padding from printtoPDF, hid mycrozine template tool (need to fix sync), cleaned up redundancies between app & board, installed marked npm package, hid markdown tool (need to fix styles)

This commit is contained in:
Jeff-Emmett 2025-01-03 09:42:53 +07:00
parent 02f816e613
commit 7b1fe2b803
13 changed files with 3027 additions and 910 deletions

3476
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,9 @@
"@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", "@types/markdown-it": "^14.1.1",
"@types/marked": "^5.0.2",
"@vercel/analytics": "^1.2.2", "@vercel/analytics": "^1.2.2",
"cherry-markdown": "^0.8.57",
"cloudflare-workers-unfurl": "^0.0.7", "cloudflare-workers-unfurl": "^0.0.7",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
@ -31,6 +33,7 @@
"jotai": "^2.6.0", "jotai": "^2.6.0",
"jspdf": "^2.5.2", "jspdf": "^2.5.2",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"marked": "^15.0.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^7.0.2", "react-router-dom": "^7.0.2",

View File

@ -6,15 +6,14 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"
import { Contact } from "@/routes/Contact" import { Contact } from "@/routes/Contact"
import { Board } from "./routes/Board" import { Board } from "./routes/Board"
import { Inbox } from "./routes/Inbox" import { Inbox } from "./routes/Inbox"
import { Editor, Tldraw, TLShapeId } from "tldraw"
import { components } from "./ui/components"
import { overrides } from "./ui/overrides"
import { ChatBoxShape } from "./shapes/ChatBoxShapeUtil" import { ChatBoxShape } from "./shapes/ChatBoxShapeUtil"
import { VideoChatShape } from "./shapes/VideoChatShapeUtil" import { VideoChatShape } from "./shapes/VideoChatShapeUtil"
import { ChatBoxTool } from "./tools/ChatBoxTool" import { ChatBoxTool } from "./tools/ChatBoxTool"
import { VideoChatTool } from "./tools/VideoChatTool" import { VideoChatTool } from "./tools/VideoChatTool"
import { EmbedTool } from "./tools/EmbedTool" import { EmbedTool } from "./tools/EmbedTool"
import { EmbedShape } from "./shapes/EmbedShapeUtil" import { EmbedShape } from "./shapes/EmbedShapeUtil"
import { MycrozineTemplateTool } from './tools/MycrozineTemplateTool'
import { MycrozineTemplateShape } from './shapes/MycrozineTemplateShapeUtil'
import { MarkdownShape } from "./shapes/MarkdownShapeUtil" import { MarkdownShape } from "./shapes/MarkdownShapeUtil"
import { MarkdownTool } from "./tools/MarkdownTool" import { MarkdownTool } from "./tools/MarkdownTool"
import { createRoot } from "react-dom/client" import { createRoot } from "react-dom/client"
@ -22,51 +21,27 @@ import { handleInitialPageLoad } from "./utils/handleInitialPageLoad"
import { DailyProvider } from "@daily-co/daily-react" import { DailyProvider } from "@daily-co/daily-react"
import Daily from "@daily-co/daily-js" import Daily from "@daily-co/daily-js"
inject() inject()
const customShapeUtils = [ const customShapeUtils = [
ChatBoxShape, ChatBoxShape,
VideoChatShape, VideoChatShape,
EmbedShape, EmbedShape,
//MarkdownShape, MycrozineTemplateShape,
MarkdownShape,
]
const customTools = [
ChatBoxTool,
VideoChatTool,
EmbedTool,
// MycrozineTemplateTool,
// MarkdownTool
] ]
const customTools = [ChatBoxTool, VideoChatTool, EmbedTool] //, MarkdownTool]
const callObject = Daily.createCallObject() const callObject = Daily.createCallObject()
export default function InteractiveShapeExample() {
return (
<div className="tldraw__editor">
<Tldraw
shapeUtils={customShapeUtils}
tools={customTools}
overrides={overrides}
components={components}
onMount={(editor) => {
handleInitialPageLoad(editor)
editor.createShape({ type: "my-interactive-shape", x: 100, y: 100 })
}}
/>
</div>
)
}
//createRoot(document.getElementById("root")!).render(<App />)
function App() { function App() {
if (process.env.NODE_ENV === "production") {
// Comment out console.log override temporarily for debugging
// console.log = () => {}
// console.debug = () => {}
// console.info = () => {}
// Keep error and warn for debugging
// console.error = () => {};
// console.warn = () => {};
}
// Add a debug message to verify console logging is working
console.log("App initialized, NODE_ENV:", process.env.NODE_ENV)
return ( return (
<DailyProvider callObject={callObject}> <DailyProvider callObject={callObject}>
<BrowserRouter> <BrowserRouter>
@ -82,3 +57,4 @@ function App() {
} }
createRoot(document.getElementById("root")!).render(<App />) createRoot(document.getElementById("root")!).render(<App />)

View File

@ -287,6 +287,58 @@ p:has(+ ol) {
& p { & p {
font-size: 1.1rem; font-size: 1.1rem;
} }
/* Markdown preview styles */
& h1 { font-size: 2em; margin: 0.67em 0; }
& h2 { font-size: 1.5em; margin: 0.75em 0; }
& h3 { font-size: 1.17em; margin: 0.83em 0; }
& h4 { margin: 1.12em 0; }
& h5 { font-size: 0.83em; margin: 1.5em 0; }
& h6 { font-size: 0.75em; margin: 1.67em 0; }
& ul, & ol {
padding-left: 2em;
margin: 1em 0;
}
& p {
margin: 1em 0;
}
& code {
background-color: #f5f5f5;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: monospace;
}
& pre {
background-color: #f5f5f5;
padding: 1em;
border-radius: 4px;
overflow-x: auto;
}
& blockquote {
margin: 1em 0;
padding-left: 1em;
border-left: 4px solid #ddd;
color: #666;
}
& table {
border-collapse: collapse;
margin: 1em 0;
}
& th, & td {
border: 1px solid #ddd;
padding: 6px 13px;
}
& tr:nth-child(2n) {
background-color: #f8f8f8;
}
} }
.transparent { .transparent {

View File

@ -17,12 +17,26 @@ import { components } from "@/ui/components"
import { overrides } from "@/ui/overrides" import { overrides } from "@/ui/overrides"
import { unfurlBookmarkUrl } from "../utils/unfurlBookmarkUrl" import { unfurlBookmarkUrl } from "../utils/unfurlBookmarkUrl"
import { handleInitialPageLoad } from "@/utils/handleInitialPageLoad" import { handleInitialPageLoad } from "@/utils/handleInitialPageLoad"
import { MycrozineTemplateTool } from "@/tools/MycrozineTemplateTool"
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
// Default to production URL if env var isn't available // Default to production URL if env var isn't available
export const WORKER_URL = "https://jeffemmett-canvas.jeffemmett.workers.dev" export const WORKER_URL = "https://jeffemmett-canvas.jeffemmett.workers.dev"
const shapeUtils = [ChatBoxShape, VideoChatShape, EmbedShape] //, MarkdownShape] const shapeUtils = [
const tools = [ChatBoxTool, VideoChatTool, EmbedTool] //, MarkdownTool] ChatBoxShape,
VideoChatShape,
EmbedShape,
// MycrozineTemplateShape,
// MarkdownShape
]
const tools = [
ChatBoxTool,
VideoChatTool,
EmbedTool,
// MycrozineTemplateTool,
// MarkdownTool
]
export function Board() { export function Board() {
const { slug } = useParams<{ slug: string }>() const { slug } = useParams<{ slug: string }>()

View File

@ -1,11 +1,38 @@
/** TODO: build this */ /** TODO: build this */
import { BaseBoxShapeUtil, TLBaseBoxShape, TLBaseShape } from "tldraw" 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< export type IMarkdownShape = TLBaseShape<
"MarkdownTool", "MarkdownTool",
{ {
content: string content: string
isPreview: boolean
w: number
h: number
color: string
size: string
font: string
} }
> >
@ -14,19 +41,148 @@ export class MarkdownShape extends BaseBoxShapeUtil<
> { > {
static override type = "MarkdownTool" static override type = "MarkdownTool"
indicator(_shape: IMarkdownShape) { styles = {
return null color: MarkdownColor,
size: MarkdownSize,
font: MarkdownFont,
} }
getDefaultProps(): IMarkdownShape["props"] & { w: number; h: number } { getDefaultProps(): IMarkdownShape["props"] & { w: number; h: number } {
return { console.log('getDefaultProps called');
const props = {
content: "", content: "",
w: 100, isPreview: false,
h: 100, 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) { component(shape: IMarkdownShape) {
return <div>{shape.props.content}</div> 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>
)
}

View File

@ -0,0 +1,81 @@
import { BaseBoxShapeUtil, TLBaseShape, TLResizeInfo} from "@tldraw/tldraw"
export type IMycrozineTemplateShape = TLBaseShape<
"MycrozineTemplate",
{
w: number
h: number
}
>
export class MycrozineTemplateShape extends BaseBoxShapeUtil<IMycrozineTemplateShape> {
static override type = "MycrozineTemplate"
getDefaultProps(): IMycrozineTemplateShape["props"] {
// 8.5" × 11" at 300 DPI = 2550 × 3300 pixels
const props = {
w: 2550,
h: 3300,
}
console.log('MycrozineTemplate - Default props:', props)
return props
}
indicator(shape: IMycrozineTemplateShape) {
return (
<g>
<rect x={0} y={0} width={shape.props.w} height={shape.props.h} />
</g>
)
}
containerStyle = {
position: 'relative' as const,
backgroundColor: 'transparent',
border: '1px solid #666',
borderRadius: '2px',
}
verticalGuideStyle = {
position: 'absolute' as const,
left: '50%',
top: 0,
bottom: 0,
borderLeft: '1px dashed #666',
}
horizontalGuideStyle = {
position: 'absolute' as const,
left: 0,
right: 0,
borderTop: '1px dashed #666',
}
component(shape: IMycrozineTemplateShape) {
const { w, h } = shape.props
const isSelected = this.editor.getSelectedShapeIds().includes(shape.id)
return (
<div
style={{
...this.containerStyle,
width: `${w}px`,
height: `${h}px`,
pointerEvents: isSelected ? 'none' : 'all'
}}
>
<div style={this.verticalGuideStyle} />
{[0.25, 0.5, 0.75].map((ratio, index) => (
<div
key={index}
style={{
...this.horizontalGuideStyle,
top: `${ratio * 100}%`,
}}
/>
))}
</div>
)
}
}

View File

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

View File

@ -130,7 +130,18 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
editor.setCurrentTool("Embed") editor.setCurrentTool("Embed")
}} }}
/> />
{/* <TldrawUiMenuItem {/*
<TldrawUiMenuItem
id="mycrozine-template"
label="Create Mycrozine Template"
icon="rectangle"
kbd="m"
disabled={hasSelection}
onSelect={() => {
editor.setCurrentTool("MycrozineTemplate")
}}
/>
<TldrawUiMenuItem
id="markdown" id="markdown"
label="Create Markdown" label="Create Markdown"
icon="markdown" icon="markdown"
@ -139,7 +150,8 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
onSelect={() => { onSelect={() => {
editor.setCurrentTool("Markdown") editor.setCurrentTool("Markdown")
}} }}
/> */} />
*/}
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
{/* Frame Controls */} {/* Frame Controls */}

View File

@ -44,6 +44,7 @@ export function CustomToolbar() {
isSelected={tools["Embed"].id === editor.getCurrentToolId()} isSelected={tools["Embed"].id === editor.getCurrentToolId()}
/> />
)} )}
{/*
{tools["Markdown"] && ( {tools["Markdown"] && (
<TldrawUiMenuItem <TldrawUiMenuItem
{...tools["Markdown"]} {...tools["Markdown"]}
@ -52,6 +53,7 @@ export function CustomToolbar() {
isSelected={tools["Markdown"].id === editor.getCurrentToolId()} isSelected={tools["Markdown"].id === editor.getCurrentToolId()}
/> />
)} )}
*/}
</DefaultToolbar> </DefaultToolbar>
) )
} }

View File

@ -63,14 +63,24 @@ export const overrides: TLUiOverrides = {
readonlyOk: true, readonlyOk: true,
onSelect: () => editor.setCurrentTool("Embed"), onSelect: () => editor.setCurrentTool("Embed"),
}, },
// Markdown: { /*
// id: "Markdown", Markdown: {
// icon: "markdown", id: "Markdown",
// label: "Markdown", icon: "markdown",
// kbd: "alt+m", label: "Markdown",
// readonlyOk: true, kbd: "alt+m",
// onSelect: () => editor.setCurrentTool("Markdown"), readonlyOk: true,
// }, onSelect: () => editor.setCurrentTool("Markdown"),
},
MycrozineTemplate: {
id: "MycrozineTemplate",
icon: "rectangle",
label: "Mycrozine Template",
kbd: "m",
readonlyOk: true,
onSelect: () => editor.setCurrentTool("MycrozineTemplate"),
},
*/
} }
}, },
actions(editor, actions) { actions(editor, actions) {
@ -124,33 +134,6 @@ export const overrides: TLUiOverrides = {
}, },
readonlyOk: true, readonlyOk: true,
}, },
// TODO: FIX THIS
handleSelectedShapeDrag: {
id: "handle-selected-shape-drag",
label: "Drag Selected Shape",
onSelect: (info: any) => {
const shape = editor.getShapeAtPoint(info.point)
if (shape && editor.getSelectedShapeIds().includes(shape.id)) {
if (editor.isPointInShape(shape, info.point)) {
editor.dispatch({
type: "pointer",
name: "pointer_down",
point: info.point,
button: info.button,
shiftKey: info.shiftKey,
altKey: info.altKey,
ctrlKey: info.ctrlKey,
metaKey: info.metaKey,
accelKey: info.ctrlKey || info.metaKey,
pointerId: info.pointerId,
target: "shape",
shape,
isPen: false,
})
}
}
},
},
moveSelectedLeft: { moveSelectedLeft: {
id: "move-selected-left", id: "move-selected-left",
label: "Move Left", label: "Move Left",

View File

@ -19,7 +19,7 @@ export const saveToPdf = async (editor: Editor) => {
opts: { opts: {
scale: 2, scale: 2,
background: true, background: true,
padding: 10, padding: 0,
preserveAspectRatio: "true", preserveAspectRatio: "true",
}, },
}) })

View File

@ -15,6 +15,7 @@ import { ChatBoxShape } from "@/shapes/ChatBoxShapeUtil"
import { VideoChatShape } from "@/shapes/VideoChatShapeUtil" import { VideoChatShape } from "@/shapes/VideoChatShapeUtil"
import { EmbedShape } from "@/shapes/EmbedShapeUtil" import { EmbedShape } from "@/shapes/EmbedShapeUtil"
import { MarkdownShape } from "@/shapes/MarkdownShapeUtil" import { MarkdownShape } from "@/shapes/MarkdownShapeUtil"
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
// add custom shapes and bindings here if needed: // add custom shapes and bindings here if needed:
export const customSchema = createTLSchema({ export const customSchema = createTLSchema({
@ -36,6 +37,10 @@ export const customSchema = createTLSchema({
props: MarkdownShape.props, props: MarkdownShape.props,
migrations: MarkdownShape.migrations, migrations: MarkdownShape.migrations,
}, },
MycrozineTemplate: {
props: MycrozineTemplateShape.props,
migrations: MycrozineTemplateShape.migrations,
},
}, },
bindings: defaultBindingSchemas, bindings: defaultBindingSchemas,
}) })