From 812702aa9afbfe8de7672520852a6066341a4dc0 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 28 Feb 2026 13:51:38 -0800 Subject: Add CodeMirror 6 for markdown editing - Install CodeMirror 6 with markdown language support - Add MarkdownEditor component with: - Syntax highlighting for markdown - Line numbers - Active line highlighting - History (undo/redo) - One Dark theme - Code block language highlighting - Keep react-markdown for preview mode - Add CSS for CodeMirror integration --- renderer/src/components/DocumentPane.tsx | 100 +++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) (limited to 'renderer/src/components') diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx index 95d7d03..cf777b1 100644 --- a/renderer/src/components/DocumentPane.tsx +++ b/renderer/src/components/DocumentPane.tsx @@ -1,6 +1,12 @@ -import React, { useState } from "react"; +import React, { useState, useRef, useEffect } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; +import { EditorState } from "@codemirror/state"; +import { EditorView, keymap, lineNumbers, highlightActiveLine } from "@codemirror/view"; +import { markdown } from "@codemirror/lang-markdown"; +import { languages } from "@codemirror/language-data"; +import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; +import { oneDark } from "@codemirror/theme-one-dark"; import type { Phase } from "../types"; interface DocumentPaneProps { @@ -11,6 +17,90 @@ interface DocumentPaneProps { showOnboarding?: boolean; } +function MarkdownEditor({ + content, + onChange, + disabled, +}: { + content: string; + onChange: (content: string) => void; + disabled: boolean; +}) { + const editorRef = useRef(null); + const viewRef = useRef(null); + + useEffect(() => { + if (!editorRef.current) return; + + const updateListener = EditorView.updateListener.of((update) => { + if (update.docChanged) { + onChange(update.state.doc.toString()); + } + }); + + const state = EditorState.create({ + doc: content, + extensions: [ + lineNumbers(), + highlightActiveLine(), + history(), + keymap.of([...defaultKeymap, ...historyKeymap]), + markdown({ codeLanguages: languages }), + oneDark, + updateListener, + EditorView.editable.of(!disabled), + EditorView.theme({ + "&": { + height: "100%", + fontSize: "14px", + }, + ".cm-scroller": { + overflow: "auto", + fontFamily: '"SF Mono", Monaco, "Cascadia Code", monospace', + }, + ".cm-content": { + padding: "16px 0", + }, + ".cm-line": { + padding: "0 16px", + }, + }), + ], + }); + + const view = new EditorView({ + state, + parent: editorRef.current, + }); + + viewRef.current = view; + + return () => { + view.destroy(); + viewRef.current = null; + }; + }, [disabled]); + + // Update content when it changes externally + useEffect(() => { + const view = viewRef.current; + if (!view) return; + + const currentContent = view.state.doc.toString(); + if (content !== currentContent) { + view.dispatch({ + changes: { + from: 0, + to: currentContent.length, + insert: content, + }, + }); + } + }, [content]); + + return
; +} + export function DocumentPane({ content, onChange, @@ -115,12 +205,10 @@ export function DocumentPane({
{isEditing ? ( -