feat: add markdown viewer toggle in PlanView

Enable switching between preview and edit modes for Markdown content
Persist viewer mode in localStorage and restore on load
Render PreviewToggleButton beside the path actions
This commit is contained in:
Bohdan Triapitsyn
2026-01-27 12:33:12 +02:00
parent 829e9f6216
commit d17b617453
+50 -1
View File
@@ -1,5 +1,8 @@
import React from 'react'; import React from 'react';
import { CodeMirrorEditor } from '@/components/ui/CodeMirrorEditor'; import { CodeMirrorEditor } from '@/components/ui/CodeMirrorEditor';
import { PreviewToggleButton } from './PreviewToggleButton';
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -114,11 +117,36 @@ export const PlanView: React.FC = () => {
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [copiedPath, setCopiedPath] = React.useState(false); const [copiedPath, setCopiedPath] = React.useState(false);
const [copiedContent, setCopiedContent] = React.useState(false); const [copiedContent, setCopiedContent] = React.useState(false);
const [mdViewMode, setMdViewMode] = React.useState<'preview' | 'edit'>('edit');
const copiedTimeoutRef = React.useRef<number | null>(null); const copiedTimeoutRef = React.useRef<number | null>(null);
const copiedContentTimeoutRef = React.useRef<number | null>(null); const copiedContentTimeoutRef = React.useRef<number | null>(null);
const [lineSelection, setLineSelection] = React.useState<SelectedLineRange | null>(null); const [lineSelection, setLineSelection] = React.useState<SelectedLineRange | null>(null);
const [commentText, setCommentText] = React.useState(''); const [commentText, setCommentText] = React.useState('');
const MD_VIEWER_MODE_KEY = 'openchamber:plan:md-viewer-mode';
React.useEffect(() => {
try {
const stored = localStorage.getItem(MD_VIEWER_MODE_KEY);
if (!stored) return;
const parsed = JSON.parse(stored) as unknown;
if (parsed === 'preview' || parsed === 'edit') {
setMdViewMode(parsed);
}
} catch {
// ignore
}
}, []);
const saveMdViewMode = React.useCallback((mode: 'preview' | 'edit') => {
setMdViewMode(mode);
try {
localStorage.setItem(MD_VIEWER_MODE_KEY, JSON.stringify(mode));
} catch {
// ignore
}
}, []);
const isSelectingRef = React.useRef(false); const isSelectingRef = React.useRef(false);
const selectionStartRef = React.useRef<number | null>(null); const selectionStartRef = React.useRef<number | null>(null);
@@ -421,6 +449,10 @@ export const PlanView: React.FC = () => {
</div> </div>
{resolvedPath ? ( {resolvedPath ? (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<PreviewToggleButton
currentMode={mdViewMode}
onToggle={() => saveMdViewMode(mdViewMode === 'preview' ? 'edit' : 'preview')}
/>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -496,6 +528,22 @@ export const PlanView: React.FC = () => {
}} }}
> >
<div className="h-full oc-plan-editor"> <div className="h-full oc-plan-editor">
{mdViewMode === 'preview' ? (
<div className="h-full overflow-auto p-3">
<ErrorBoundary
fallback={
<div className="rounded-md border border-destructive/20 bg-destructive/10 px-3 py-2">
<div className="mb-1 font-medium text-destructive">Preview unavailable</div>
<div className="text-sm text-muted-foreground">
Switch to edit mode to fix the issue.
</div>
</div>
}
>
<SimpleMarkdownRenderer content={content} className="typography-markdown-body" />
</ErrorBoundary>
</div>
) : (
<CodeMirrorEditor <CodeMirrorEditor
value={content} value={content}
onChange={() => { onChange={() => {
@@ -558,6 +606,7 @@ export const PlanView: React.FC = () => {
}, },
}} }}
/> />
)}
</div> </div>
</div> </div>
</div> </div>
@@ -565,7 +614,7 @@ export const PlanView: React.FC = () => {
</ScrollableOverlay> </ScrollableOverlay>
</div> </div>
{lineSelection && ( {lineSelection && mdViewMode !== 'preview' && (
<div <div
className="pointer-events-none absolute inset-0 z-50 flex flex-col justify-end" className="pointer-events-none absolute inset-0 z-50 flex flex-col justify-end"
style={{ paddingBottom: isMobile ? 'var(--oc-keyboard-inset, 0px)' : '0px' }} style={{ paddingBottom: isMobile ? 'var(--oc-keyboard-inset, 0px)' : '0px' }}