feat: implement WYSIWYG markdown editor

Add complete markdown editor with Go backend and React/TypeScript frontend.

Backend:
- Cobra CLI with configurable host, port, data-dir, static-dir flags
- REST API for CRUD operations on markdown files (GET, POST, PUT, DELETE)
- File storage with flat .md structure
- Comprehensive Logrus logging for all operations
- Static asset serving for frontend

Frontend:
- React 18 + TypeScript + Tailwind CSS
- Live markdown editor with GFM preview (react-markdown)
- File management UI (list, create, open, save, delete)
- Theme system (Light/Dark/System) with localStorage persistence
- Responsive design (320px - 1920px+)

Testing:
- 6 backend tests covering CRUD round-trip, validation, error handling
- 19 frontend tests covering API, theme system, and UI components
- All tests passing with single 'make test' command

Build:
- Frontend compiles to optimized assets in dist/
- Backend can serve frontend via --static-dir flag
This commit is contained in:
2026-02-06 08:53:52 -05:00
parent 5782d08950
commit a80de1730c
36 changed files with 9646 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
interface EditorProps {
content: string;
onChange: (content: string) => void;
placeholder?: string;
}
export function Editor({ content, onChange, placeholder = '# Start writing\n\nYour markdown here...' }: EditorProps) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 h-[calc(100vh-160px)] min-h-[300px]">
<div className="border-r border-gray-200 dark:border-gray-700">
<textarea
value={content}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="w-full h-full p-4 resize-none outline-none bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-mono text-sm leading-relaxed"
spellCheck={false}
/>
</div>
<div className="p-4 overflow-auto bg-gray-50 dark:bg-gray-800">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
className="prose dark:prose-invert max-w-none"
>
{content || placeholder}
</ReactMarkdown>
</div>
</div>
);
}