diff options
Diffstat (limited to 'renderer/src/components/DocumentPane.tsx')
| -rw-r--r-- | renderer/src/components/DocumentPane.tsx | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx new file mode 100644 index 0000000..e44b0fb --- /dev/null +++ b/renderer/src/components/DocumentPane.tsx | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | import React, { useState, useMemo } from "react"; | ||
| 2 | import type { Phase } from "../types"; | ||
| 3 | |||
| 4 | interface DocumentPaneProps { | ||
| 5 | content: string; | ||
| 6 | onChange: (content: string) => void; | ||
| 7 | phase: Phase; | ||
| 8 | disabled: boolean; | ||
| 9 | } | ||
| 10 | |||
| 11 | function renderMarkdown(md: string): string { | ||
| 12 | return ( | ||
| 13 | md | ||
| 14 | // Headers | ||
| 15 | .replace(/^### (.*$)/gm, "<h3>$1</h3>") | ||
| 16 | .replace(/^## (.*$)/gm, "<h2>$1</h2>") | ||
| 17 | .replace(/^# (.*$)/gm, "<h1>$1</h1>") | ||
| 18 | // Bold/italic | ||
| 19 | .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>") | ||
| 20 | .replace(/\*([^*]+)\*/g, "<em>$1</em>") | ||
| 21 | // Code blocks | ||
| 22 | .replace( | ||
| 23 | /```(\w*)\n([\s\S]*?)```/g, | ||
| 24 | '<pre><code class="language-$1">$2</code></pre>' | ||
| 25 | ) | ||
| 26 | .replace(/`([^`]+)`/g, "<code>$1</code>") | ||
| 27 | // Lists | ||
| 28 | .replace(/^- \[x\] (.*$)/gm, '<li class="task done">☑ $1</li>') | ||
| 29 | .replace(/^- \[ \] (.*$)/gm, '<li class="task">☐ $1</li>') | ||
| 30 | .replace(/^- (.*$)/gm, "<li>$1</li>") | ||
| 31 | // Review comments (highlight them) | ||
| 32 | .replace(/(\/\/ REVIEW:.*$)/gm, '<mark class="review">$1</mark>') | ||
| 33 | .replace(/(\/\/ NOTE:.*$)/gm, '<mark class="note">$1</mark>') | ||
| 34 | // Paragraphs | ||
| 35 | .replace(/\n\n/g, "</p><p>") | ||
| 36 | .replace(/^(.+)$/gm, "<p>$1</p>") | ||
| 37 | // Clean up | ||
| 38 | .replace(/<p><\/p>/g, "") | ||
| 39 | .replace(/<p>(<h[1-3]>)/g, "$1") | ||
| 40 | .replace(/(<\/h[1-3]>)<\/p>/g, "$1") | ||
| 41 | .replace(/<p>(<pre>)/g, "$1") | ||
| 42 | .replace(/(<\/pre>)<\/p>/g, "$1") | ||
| 43 | .replace(/<p>(<li)/g, "$1") | ||
| 44 | .replace(/(<\/li>)<\/p>/g, "$1") | ||
| 45 | ); | ||
| 46 | } | ||
| 47 | |||
| 48 | export function DocumentPane({ | ||
| 49 | content, | ||
| 50 | onChange, | ||
| 51 | phase, | ||
| 52 | disabled, | ||
| 53 | }: DocumentPaneProps) { | ||
| 54 | const [isEditing, setIsEditing] = useState(false); | ||
| 55 | const renderedHtml = useMemo(() => renderMarkdown(content), [content]); | ||
| 56 | |||
| 57 | if (phase === "implement") { | ||
| 58 | return ( | ||
| 59 | <div className="document-pane"> | ||
| 60 | <div className="document-header"> | ||
| 61 | <span>plan.md</span> | ||
| 62 | <span className="badge">Implementing...</span> | ||
| 63 | </div> | ||
| 64 | <div | ||
| 65 | className="document-content rendered" | ||
| 66 | dangerouslySetInnerHTML={{ __html: renderedHtml }} | ||
| 67 | /> | ||
| 68 | </div> | ||
| 69 | ); | ||
| 70 | } | ||
| 71 | |||
| 72 | const filename = phase === "research" ? "research.md" : "plan.md"; | ||
| 73 | |||
| 74 | return ( | ||
| 75 | <div className="document-pane"> | ||
| 76 | <div className="document-header"> | ||
| 77 | <span>{filename}</span> | ||
| 78 | <button onClick={() => setIsEditing(!isEditing)}> | ||
| 79 | {isEditing ? "Preview" : "Edit"} | ||
| 80 | </button> | ||
| 81 | </div> | ||
| 82 | |||
| 83 | {isEditing ? ( | ||
| 84 | <textarea | ||
| 85 | className="document-content editing" | ||
| 86 | value={content} | ||
| 87 | onChange={(e) => onChange(e.target.value)} | ||
| 88 | disabled={disabled} | ||
| 89 | placeholder={`${filename} will appear here...`} | ||
| 90 | /> | ||
| 91 | ) : ( | ||
| 92 | <div | ||
| 93 | className="document-content rendered" | ||
| 94 | dangerouslySetInnerHTML={{ | ||
| 95 | __html: | ||
| 96 | renderedHtml || | ||
| 97 | '<p class="empty">Document will appear here after Claude generates it...</p>', | ||
| 98 | }} | ||
| 99 | onClick={() => !disabled && setIsEditing(true)} | ||
| 100 | /> | ||
| 101 | )} | ||
| 102 | </div> | ||
| 103 | ); | ||
| 104 | } | ||
