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,34 @@
package router
import (
"net/http"
"github.com/evanreichard/markdown-editor/internal/api"
"github.com/evanreichard/markdown-editor/internal/logging"
)
// New creates a new HTTP router with all routes configured
func New(handlers *api.Handlers, staticDir string) *http.ServeMux {
mux := http.NewServeMux()
// API routes
mux.HandleFunc("GET /api/files", handlers.ListFiles)
mux.HandleFunc("GET /api/files/", handlers.GetFile)
mux.HandleFunc("POST /api/files", handlers.CreateFile)
mux.HandleFunc("PUT /api/files/", handlers.UpdateFile)
mux.HandleFunc("DELETE /api/files/", handlers.DeleteFile)
// Static assets
if staticDir != "" {
logging.Logger.WithField("static_dir", staticDir).Info("Static assets configured")
fs := http.FileServer(http.Dir(staticDir))
mux.Handle("/", fs)
} else {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Static assets directory not configured", http.StatusNotFound)
})
}
logging.Logger.Info("Router initialized with all routes")
return mux
}