From b1163c3232430283d3a084304e314f93cf2bb711 Mon Sep 17 00:00:00 2001 From: Hiren Kangad Date: Fri, 13 Mar 2026 09:49:16 +0530 Subject: [PATCH] feat(markdown): integrate undo/redo for edit mode and checkbox toggles --- frontend/src/hooks/useCanvasSetup.ts | 1 + frontend/src/pages/Editor.tsx | 63 +++++++++++++++++++++------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/frontend/src/hooks/useCanvasSetup.ts b/frontend/src/hooks/useCanvasSetup.ts index badfc36..89eacd4 100644 --- a/frontend/src/hooks/useCanvasSetup.ts +++ b/frontend/src/hooks/useCanvasSetup.ts @@ -161,6 +161,7 @@ export function useCanvasSetup(deps: CanvasSetupDeps) { // Double-click markdown → enter edit mode selection.onDoubleClickMarkdown = (item) => { if (mdOverlayRef.current) { + undoRef.current?.saveState(); mdOverlayRef.current.setEditing(item.id); selection.setEnabled(false); } diff --git a/frontend/src/pages/Editor.tsx b/frontend/src/pages/Editor.tsx index 91ed6a3..b9f402d 100644 --- a/frontend/src/pages/Editor.tsx +++ b/frontend/src/pages/Editor.tsx @@ -43,6 +43,7 @@ import { StickySprite } from '../canvas/sprites/StickySprite'; import * as ops from '../canvas/operations'; import ReactDOM from 'react-dom'; import MarkdownReadView from '../components/MarkdownReadView'; +const LazyMarkdownEditView = React.lazy(() => import('../components/MarkdownEditView')); // Hooks import { useBoardLoader } from '../hooks/useBoardLoader'; @@ -207,6 +208,7 @@ export default function Editor({ isPublicView }: EditorProps) { // ── Markdown overlay — sync visible card IDs for React portal rendering ── const [mdCardIds, setMdCardIds] = useState([]); + const [editingMdId, setEditingMdId] = useState(null); useEffect(() => { if (!mdOverlay) return; @@ -227,8 +229,12 @@ export default function Editor({ isPublicView }: EditorProps) { }; mdOverlay.onChange = syncIds; + mdOverlay.onRequestEdit = (id) => setEditingMdId(id); syncIds(); - return () => { mdOverlay.onChange = null; }; + return () => { + mdOverlay.onChange = null; + mdOverlay.onRequestEdit = null; + }; }, [mdOverlay]); // Build canvas objects map for FeedbackPanel (memoized on object count changes) @@ -466,6 +472,11 @@ export default function Editor({ isPublicView }: EditorProps) { broadcastElements: (ids) => syncRef.current?.broadcastElements(ids), textEditor: textEditor ?? undefined, switchToSelect: () => setActiveTool(ToolType.SELECT), + onMarkdownCreated: (id) => { + setEditingMdId(id); + mdOverlay?.setEditing(id); + selectionRef.current?.setEnabled(false); + }, }; // reviewMode is read at click time via getter so it stays current Object.defineProperty(ctx, 'reviewMode', { get: () => reviewModeRef.current }); @@ -1213,20 +1224,44 @@ export default function Editor({ isPublicView }: EditorProps) { const item = canvasRef.current?.getScene()?.getById(id); if (!mountPoint || !item || item.type !== 'markdown') return null; const data = item.data as MarkdownObject; + const isEditing = editingMdId === id; return ReactDOM.createPortal( - { - data.content = newContent; - onCanvasChange([id]); - mdOverlay?.measureHeight(id); - }} - />, + isEditing ? ( + Loading editor...}> + { + data.content = newContent; + setEditingMdId(null); + mdOverlay?.setEditing(null); + selectionRef.current?.setEnabled(true); + onCanvasChange([id]); + mdOverlay?.measureHeight(id); + }} + onCancel={() => { + setEditingMdId(null); + mdOverlay?.setEditing(null); + selectionRef.current?.setEnabled(true); + }} + /> + + ) : ( + { + data.content = newContent; + onCanvasChange([id]); + mdOverlay?.measureHeight(id); + }} + /> + ), mountPoint, ); })}