A deployable markdown editor that connects with your self hosted files and lets you edit in a beautiful interface
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add UX polish: loading states, empty states, toast notifications, and comprehensive documentation

- Create reusable Loading and EmptyState components with brutalist design
- Replace basic spinners with styled Loading component throughout app
- Add EmptyState for no files/repos scenarios with helpful messaging
- Install and integrate sonner for toast notifications
- Add toast notifications for save errors, publish success/failures
- Update DashboardApp to include Toaster with custom styling
- Improve error handling in EditorContainer and SetupWizard
- Create comprehensive README.md with features, setup, architecture
- Create detailed SETUP.md with deployment guides and troubleshooting
- Update .env.example to match backend environment variables
- Fix docker-compose.yml environment variables and health checks
- Update frontend port from 4321 to 3000 in docker-compose

This completes Phase 4 polish and documentation tasks.

+945 -114
+18 -23
.env.example
··· 1 - # Server Configuration 2 - PORT=8080 3 - FRONTEND_URL=http://localhost:3000 4 - ALLOWED_ORIGINS=http://localhost:3000 5 - 6 1 # GitHub OAuth 7 2 # Create OAuth App at: https://github.com/settings/developers 8 - GITHUB_CLIENT_ID=your_github_client_id_here 9 - GITHUB_CLIENT_SECRET=your_github_client_secret_here 10 - GITHUB_REDIRECT_URL=http://localhost:8080/api/auth/github/callback 3 + # Callback URL: http://localhost:8080/auth/github/callback 4 + GITHUB_CLIENT_ID=your_github_oauth_client_id_here 5 + GITHUB_CLIENT_SECRET=your_github_oauth_client_secret_here 11 6 12 - # Session Management 7 + # Session Security 13 8 # Generate with: openssl rand -base64 32 14 - SESSION_SECRET=your-random-session-secret-min-32-characters 15 - SESSION_SECURE=false # Set to true in production (requires HTTPS) 16 - SESSION_MAX_AGE=86400 # 24 hours in seconds 9 + SESSION_SECRET=generate-a-random-32-character-string-here 17 10 18 11 # Database 19 12 DATABASE_PATH=./data/markedit.db 20 13 21 - # Git Operations 14 + # Git Cache 22 15 GIT_CACHE_DIR=./data/repos 23 - GIT_AUTHOR_NAME=MarkEdit 24 - GIT_AUTHOR_EMAIL=markedit@example.com 16 + 17 + # CORS (comma-separated allowed origins) 18 + CORS_ALLOWED_ORIGINS=http://localhost:4321,http://localhost:3000 25 19 26 - # Logging 27 - LOG_LEVEL=info # Options: debug, info, warn, error 20 + # Server 21 + PORT=8080 28 22 29 - # Development vs Production 30 - # For production, update: 31 - # - FRONTEND_URL=https://yourdomain.com 32 - # - ALLOWED_ORIGINS=https://yourdomain.com 33 - # - SESSION_SECURE=true 34 - # - LOG_LEVEL=info 23 + # Production Notes: 24 + # For production deployment: 25 + # - Create separate GitHub OAuth app with production URLs 26 + # - Use HTTPS callback URL: https://yourdomain.com/auth/github/callback 27 + # - Generate strong SESSION_SECRET (32+ characters) 28 + # - Update CORS_ALLOWED_ORIGINS to your production domain 29 + # - Consider using environment-specific .env files
+230 -60
README.md
··· 1 1 # MarkEdit 2 2 3 - A markdown editor for managing blog posts across multiple storage providers (GitHub, Google Drive, Dropbox). Built with Go and Astro/React, featuring WYSIWYG editing powered by TipTap. 3 + A modern, git-based markdown editor for managing blog posts stored in GitHub repositories. Edit your markdown files with a beautiful WYSIWYG editor, track changes with automatic version control, and publish updates via pull requests. 4 4 5 5 ## Features 6 6 7 - - **Multi-source Support**: Connect to GitHub repositories (Google Drive and Dropbox coming later) 8 - - **WYSIWYG Editing**: TipTap-based markdown editor with live preview 9 - - **Git Workflow**: Automatic branch management, commits, and pull requests 10 - - **Self-hosted**: Run anywhere with Docker 11 - - **Lightweight**: Single binary deployment 7 + - **🎨 Beautiful WYSIWYG Editor** - Rich text editing powered by TipTap with markdown support 8 + - **📝 Frontmatter Support** - Edit YAML frontmatter metadata alongside your content 9 + - **💾 Auto-save** - Changes are automatically saved as drafts every 2 seconds 10 + - **🔄 Git Integration** - Automatic branching, commits, and pull request creation 11 + - **🔐 GitHub OAuth** - Secure authentication with your GitHub account 12 + - **📂 Multi-repo Support** - Work with multiple repositories and folder structures 13 + - **⚡ Fast & Lightweight** - Built with modern technologies for optimal performance 14 + - **🐳 Docker Ready** - Easy deployment with Docker and docker-compose 15 + 16 + ## Tech Stack 17 + 18 + ### Backend 19 + - **Go 1.24+** - High-performance backend server 20 + - **chi** - Lightweight router 21 + - **go-git** - Native Git operations 22 + - **SQLite** (modernc.org/sqlite) - Pure Go database, no CGO required 23 + - **goth** - GitHub OAuth authentication 24 + 25 + ### Frontend 26 + - **Astro** - Fast, client-only static site generation 27 + - **React 18** - UI components 28 + - **TipTap** - WYSIWYG markdown editor 29 + - **shadcn/ui** - Beautiful, accessible components 30 + - **Tailwind CSS** - Utility-first styling 31 + - **React Query** - Data fetching and state management 12 32 13 33 ## Quick Start 14 34 15 35 ### Prerequisites 16 36 17 - - Go 1.21+ 18 - - Node.js 18+ 19 - - Docker (optional, for containerized deployment) 20 - - GitHub OAuth App credentials 37 + - Docker and Docker Compose installed 38 + - GitHub OAuth application credentials ([Setup Guide](./SETUP.md)) 39 + 40 + ### Run with Docker 41 + 42 + 1. Clone the repository: 43 + ```bash 44 + git clone https://github.com/yourusername/markedit.git 45 + cd markedit 46 + ``` 21 47 22 - ### Development Setup 48 + 2. Copy the environment file and configure: 49 + ```bash 50 + cp .env.example .env 51 + # Edit .env with your GitHub OAuth credentials 52 + ``` 23 53 24 - 1. **Create GitHub OAuth App** 25 - - Go to https://github.com/settings/developers 26 - - Create new OAuth App 27 - - Set callback URL: `http://localhost:8080/api/auth/github/callback` 28 - - Save Client ID and Client Secret 54 + 3. Start the application: 55 + ```bash 56 + make up 57 + ``` 58 + 59 + 4. Open your browser to `http://localhost:3000` 60 + 61 + That's it! The application will be running with all dependencies containerized. 62 + 63 + ## Development 64 + 65 + ### Local Development Setup 66 + 67 + #### Backend 68 + 69 + Requirements: 70 + - Go 1.24 or higher 29 71 30 - 2. **Configure Environment** 31 - ```bash 32 - cp .env.example .env 33 - # Edit .env with your GitHub OAuth credentials 34 - ``` 72 + ```bash 73 + cd backend 74 + cp .env.example .env 75 + # Edit .env with your credentials 35 76 36 - 3. **Run Backend** 37 - ```bash 38 - cd backend 39 - go mod download 40 - go run cmd/server/main.go 41 - ``` 77 + # Run migrations and start server 78 + go run cmd/server/main.go 79 + ``` 42 80 43 - 4. **Run Frontend** 44 - ```bash 45 - cd frontend 46 - npm install 47 - npm run dev 48 - ``` 81 + The backend will start on `http://localhost:8080` 49 82 50 - 5. **Access Application** 51 - - Open http://localhost:3000 52 - - Login with GitHub 53 - - Start editing! 83 + #### Frontend 54 84 55 - ### Docker Deployment 85 + Requirements: 86 + - Bun 1.x or higher 56 87 57 88 ```bash 58 - docker-compose up --build 89 + cd frontend 90 + bun install 91 + bun run dev 59 92 ``` 60 93 61 - Access at http://localhost:3000 94 + The frontend will start on `http://localhost:4321` 95 + 96 + ## How It Works 97 + 98 + ### Workflow 99 + 100 + 1. **Authenticate** - Sign in with your GitHub account 101 + 2. **Select Repository** - Choose the GitHub repo containing your blog posts 102 + 3. **Select Folder** - Optionally specify a folder within the repo 103 + 4. **Edit Files** - Browse and edit markdown files with the WYSIWYG editor 104 + 5. **Auto-save** - Changes are automatically saved as drafts 105 + 6. **Publish** - Create a pull request with your changes 106 + 107 + ### Git Integration 108 + 109 + MarkEdit uses a smart branching strategy: 110 + 111 + - Creates timestamped branches: `markedit-{unix_timestamp}` 112 + - Reuses branches if they're less than 4 hours old 113 + - Automatically commits your changes with descriptive messages 114 + - Creates pull requests with your custom title and description 115 + - Cleans up drafts after successful publication 116 + 117 + ### Draft System 118 + 119 + - All edits are saved as drafts in the SQLite database 120 + - Drafts are user-specific and file-specific 121 + - Published changes replace the draft content 122 + - Drafts are automatically deleted after successful PR creation 62 123 63 124 ## Architecture 64 125 65 - - **Backend**: Go with Chi router, GitHub OAuth, go-git for version control 66 - - **Frontend**: Astro (client-only) + React + TipTap editor 67 - - **Database**: SQLite for session and state management 68 - - **Deployment**: Single Docker container 126 + ``` 127 + markedit/ 128 + ├── backend/ # Go server 129 + │ ├── cmd/server/ # Main entry point 130 + │ ├── internal/ 131 + │ │ ├── api/ # HTTP handlers 132 + │ │ ├── auth/ # OAuth & sessions 133 + │ │ ├── database/ # SQLite & queries 134 + │ │ ├── git/ # Git operations 135 + │ │ └── markdown/ # Frontmatter parsing 136 + │ └── Dockerfile 137 + ├── frontend/ # Astro + React 138 + │ ├── src/ 139 + │ │ ├── components/ 140 + │ │ ├── lib/ # API client & hooks 141 + │ │ └── pages/ 142 + │ ├── nginx.conf 143 + │ └── Dockerfile 144 + └── docker-compose.yml 145 + ``` 69 146 70 - ## Project Status 147 + ## API Endpoints 148 + 149 + ### Authentication 150 + - `GET /auth/github` - Initiate GitHub OAuth flow 151 + - `GET /auth/github/callback` - OAuth callback handler 152 + - `GET /auth/user` - Get current user info 153 + - `POST /auth/logout` - Logout current user 154 + 155 + ### Repositories 156 + - `GET /api/repos` - List user's repositories 157 + - `GET /api/repos/:owner/:repo/files` - List files in repository 158 + - `GET /api/repos/:owner/:repo/files/*path` - Get file content 159 + - `PUT /api/repos/:owner/:repo/files/*path` - Save draft content 160 + 161 + ### Git Operations 162 + - `GET /api/repos/:owner/:repo/branch/status` - Get current branch info 163 + - `POST /api/repos/:owner/:repo/publish` - Commit, push, and create PR 164 + 165 + ## Configuration 166 + 167 + ### Environment Variables 168 + 169 + See [SETUP.md](./SETUP.md) for detailed configuration instructions. 170 + 171 + **Backend** (`.env`): 172 + ```bash 173 + GITHUB_CLIENT_ID=your_github_oauth_client_id 174 + GITHUB_CLIENT_SECRET=your_github_oauth_client_secret 175 + SESSION_SECRET=random-32-character-string 176 + DATABASE_PATH=./data/markedit.db 177 + GIT_CACHE_DIR=./data/repos 178 + CORS_ALLOWED_ORIGINS=http://localhost:4321,http://localhost:3000 179 + ``` 180 + 181 + **Frontend** (`frontend/.env`): 182 + ```bash 183 + PUBLIC_API_URL=http://localhost:8080 184 + ``` 185 + 186 + ## Deployment 187 + 188 + ### Docker Production Deployment 189 + 190 + 1. Configure environment variables in `.env` 191 + 2. Build and start containers: 192 + ```bash 193 + make build 194 + make up 195 + ``` 196 + 197 + 3. Check logs: 198 + ```bash 199 + make logs 200 + ``` 71 201 72 - Currently in initial development phase. See [IMPLEMENTATION_PLAN.md](./IMPLEMENTATION_PLAN.md) for detailed roadmap. 202 + 4. Stop containers: 203 + ```bash 204 + make down 205 + ``` 73 206 74 - ### MVP Scope 207 + ### Manual Deployment 75 208 76 - - ✅ GitHub authentication 77 - - ✅ Repository and file browsing 78 - - ✅ Markdown editing with TipTap 79 - - ✅ Automatic branch management 80 - - ✅ Commit and PR creation 81 - - ❌ Image uploads (links only initially) 82 - - ❌ Search, version history, collaborative editing (post-MVP) 209 + See [SETUP.md](./SETUP.md) for detailed deployment instructions for various platforms. 83 210 84 - ## Documentation 211 + ## Makefile Commands 85 212 86 - - [Implementation Plan](./IMPLEMENTATION_PLAN.md) - Detailed technical specification 87 - - [Architecture](./IMPLEMENTATION_PLAN.md#architecture) - System design 88 - - [API Documentation](./IMPLEMENTATION_PLAN.md#api-specification) - REST API reference 213 + ```bash 214 + make build # Build Docker images 215 + make up # Start containers 216 + make down # Stop containers 217 + make restart # Restart containers 218 + make logs # View logs 219 + make logs-f # Follow logs 220 + make clean # Remove containers and volumes 221 + make shell-be # Access backend shell 222 + make shell-fe # Access frontend shell 223 + ``` 224 + 225 + ## Design Philosophy 226 + 227 + MarkEdit features a distinctive **brutalist/editorial aesthetic**: 228 + 229 + - **Typography**: Archivo Black for headings, Crimson Pro for body text 230 + - **Colors**: Amber/orange accents (#d97706) with strong black borders 231 + - **Style**: Bold 2px borders, grain texture backgrounds, no gradients 232 + - **UX**: Clear, direct interactions with immediate feedback 233 + 234 + ## Security 235 + 236 + - OAuth tokens are securely stored and never exposed to the frontend 237 + - Session cookies are encrypted and HTTP-only 238 + - File operations are scoped to authenticated user's accessible repos 239 + - Git operations use the user's GitHub token with appropriate permissions 89 240 90 241 ## Contributing 91 242 92 - This project will be open-sourced once MVP is complete. Stay tuned! 243 + Contributions are welcome! Please follow these guidelines: 244 + 245 + 1. Fork the repository 246 + 2. Create a feature branch 247 + 3. Make your changes 248 + 4. Add tests if applicable 249 + 5. Submit a pull request 93 250 94 251 ## License 95 252 96 - TBD 253 + MIT License - see [LICENSE](./LICENSE) for details 254 + 255 + ## Support 256 + 257 + For detailed setup instructions, see [SETUP.md](./SETUP.md) 258 + 259 + For issues and feature requests, please use [GitHub Issues](https://github.com/yourusername/markedit/issues) 260 + 261 + ## Acknowledgments 262 + 263 + - [TipTap](https://tiptap.dev/) - Excellent WYSIWYG editor 264 + - [go-git](https://github.com/go-git/go-git) - Pure Go git implementation 265 + - [Astro](https://astro.build/) - Fast static site generator 266 + - [shadcn/ui](https://ui.shadcn.com/) - Beautiful component library
+539
SETUP.md
··· 1 + # MarkEdit Setup Guide 2 + 3 + This guide will help you set up and deploy MarkEdit for production or development use. 4 + 5 + ## Table of Contents 6 + 7 + - [Prerequisites](#prerequisites) 8 + - [GitHub OAuth Setup](#github-oauth-setup) 9 + - [Development Setup](#development-setup) 10 + - [Docker Deployment](#docker-deployment) 11 + - [Production Deployment](#production-deployment) 12 + - [Configuration Reference](#configuration-reference) 13 + - [Troubleshooting](#troubleshooting) 14 + 15 + ## Prerequisites 16 + 17 + ### For Docker Deployment 18 + - Docker 20.x or higher 19 + - Docker Compose 2.x or higher 20 + - GitHub account with OAuth app 21 + 22 + ### For Local Development 23 + - Go 1.24 or higher 24 + - Bun 1.x or higher (for frontend) 25 + - Git 26 + - GitHub account with OAuth app 27 + 28 + ## GitHub OAuth Setup 29 + 30 + MarkEdit uses GitHub OAuth for authentication. You need to create a GitHub OAuth application: 31 + 32 + ### 1. Create OAuth Application 33 + 34 + 1. Go to [GitHub Developer Settings](https://github.com/settings/developers) 35 + 2. Click **"New OAuth App"** 36 + 3. Fill in the application details: 37 + - **Application name**: `MarkEdit` (or your preferred name) 38 + - **Homepage URL**: `http://localhost:3000` (for development) 39 + - **Authorization callback URL**: `http://localhost:8080/auth/github/callback` 40 + 4. Click **"Register application"** 41 + 42 + ### 2. Get Credentials 43 + 44 + After registration, you'll see: 45 + - **Client ID** - Copy this value 46 + - **Client Secret** - Click "Generate a new client secret" and copy the value 47 + 48 + > ⚠️ **Important**: Keep your Client Secret secure! Never commit it to version control. 49 + 50 + ### 3. Production URLs 51 + 52 + For production deployment, create a separate OAuth app with production URLs: 53 + - **Homepage URL**: `https://yourdomain.com` 54 + - **Authorization callback URL**: `https://yourdomain.com/auth/github/callback` 55 + 56 + ## Development Setup 57 + 58 + ### 1. Clone Repository 59 + 60 + ```bash 61 + git clone https://github.com/yourusername/markedit.git 62 + cd markedit 63 + ``` 64 + 65 + ### 2. Configure Environment Variables 66 + 67 + #### Backend Configuration 68 + 69 + Create `backend/.env`: 70 + 71 + ```bash 72 + cd backend 73 + cp .env.example .env 74 + ``` 75 + 76 + Edit `backend/.env`: 77 + 78 + ```bash 79 + # GitHub OAuth 80 + GITHUB_CLIENT_ID=your_github_oauth_client_id_here 81 + GITHUB_CLIENT_SECRET=your_github_oauth_client_secret_here 82 + 83 + # Session Security 84 + SESSION_SECRET=generate-a-random-32-character-string-here 85 + 86 + # Database 87 + DATABASE_PATH=./data/markedit.db 88 + 89 + # Git Cache 90 + GIT_CACHE_DIR=./data/repos 91 + 92 + # CORS (adjust based on your frontend URL) 93 + CORS_ALLOWED_ORIGINS=http://localhost:4321,http://localhost:3000 94 + 95 + # Server 96 + PORT=8080 97 + ``` 98 + 99 + **Generate SESSION_SECRET**: 100 + ```bash 101 + # On macOS/Linux: 102 + openssl rand -base64 32 103 + 104 + # Or use Go: 105 + go run -c 'package main; import ("crypto/rand"; "encoding/base64"; "fmt"); func main() { b := make([]byte, 32); rand.Read(b); fmt.Println(base64.StdEncoding.EncodeToString(b)) }' 106 + ``` 107 + 108 + #### Frontend Configuration 109 + 110 + Create `frontend/.env`: 111 + 112 + ```bash 113 + cd ../frontend 114 + echo "PUBLIC_API_URL=http://localhost:8080" > .env 115 + ``` 116 + 117 + ### 3. Run Backend 118 + 119 + ```bash 120 + cd backend 121 + 122 + # Install dependencies 123 + go mod download 124 + 125 + # Create data directory 126 + mkdir -p data 127 + 128 + # Run server 129 + go run cmd/server/main.go 130 + ``` 131 + 132 + Backend will start on `http://localhost:8080` 133 + 134 + ### 4. Run Frontend 135 + 136 + In a new terminal: 137 + 138 + ```bash 139 + cd frontend 140 + 141 + # Install dependencies 142 + bun install 143 + 144 + # Start dev server 145 + bun run dev 146 + ``` 147 + 148 + Frontend will start on `http://localhost:4321` 149 + 150 + ### 5. Access Application 151 + 152 + 1. Open browser to `http://localhost:4321` 153 + 2. Click "Sign in with GitHub" 154 + 3. Authorize the application 155 + 4. Start editing! 156 + 157 + ## Docker Deployment 158 + 159 + Docker deployment is the recommended method for production. 160 + 161 + ### 1. Configure Environment 162 + 163 + Create `.env` in the project root: 164 + 165 + ```bash 166 + cp .env.example .env 167 + ``` 168 + 169 + Edit `.env`: 170 + 171 + ```bash 172 + # GitHub OAuth (use production OAuth app) 173 + GITHUB_CLIENT_ID=your_production_github_oauth_client_id 174 + GITHUB_CLIENT_SECRET=your_production_github_oauth_client_secret 175 + 176 + # Session Security 177 + SESSION_SECRET=generate-a-secure-random-32-character-string 178 + 179 + # Database 180 + DATABASE_PATH=/app/data/markedit.db 181 + 182 + # Git Cache 183 + GIT_CACHE_DIR=/app/data/repos 184 + 185 + # CORS (adjust for your domain) 186 + CORS_ALLOWED_ORIGINS=https://yourdomain.com,http://localhost:3000 187 + 188 + # Server 189 + PORT=8080 190 + ``` 191 + 192 + ### 2. Build Images 193 + 194 + ```bash 195 + make build 196 + ``` 197 + 198 + Or manually: 199 + 200 + ```bash 201 + docker-compose build 202 + ``` 203 + 204 + ### 3. Start Services 205 + 206 + ```bash 207 + make up 208 + ``` 209 + 210 + Or manually: 211 + 212 + ```bash 213 + docker-compose up -d 214 + ``` 215 + 216 + ### 4. Verify Deployment 217 + 218 + Check if services are running: 219 + 220 + ```bash 221 + make logs 222 + ``` 223 + 224 + Or: 225 + 226 + ```bash 227 + docker-compose logs -f 228 + ``` 229 + 230 + Access the application at `http://localhost:3000` 231 + 232 + ### 5. Stop Services 233 + 234 + ```bash 235 + make down 236 + ``` 237 + 238 + ## Production Deployment 239 + 240 + ### Option 1: Docker on VPS 241 + 242 + #### 1. Server Setup 243 + 244 + On your VPS (Ubuntu/Debian): 245 + 246 + ```bash 247 + # Update system 248 + sudo apt update && sudo apt upgrade -y 249 + 250 + # Install Docker 251 + curl -fsSL https://get.docker.com -o get-docker.sh 252 + sudo sh get-docker.sh 253 + 254 + # Install Docker Compose 255 + sudo apt install docker-compose-plugin 256 + 257 + # Create app directory 258 + mkdir -p ~/markedit 259 + cd ~/markedit 260 + ``` 261 + 262 + #### 2. Deploy Application 263 + 264 + ```bash 265 + # Clone repository 266 + git clone https://github.com/yourusername/markedit.git . 267 + 268 + # Configure environment 269 + cp .env.example .env 270 + nano .env # Edit with production values 271 + 272 + # Build and start 273 + docker-compose up -d 274 + 275 + # Check logs 276 + docker-compose logs -f 277 + ``` 278 + 279 + #### 3. Setup Reverse Proxy (nginx) 280 + 281 + Install nginx: 282 + 283 + ```bash 284 + sudo apt install nginx certbot python3-certbot-nginx 285 + ``` 286 + 287 + Create nginx config `/etc/nginx/sites-available/markedit`: 288 + 289 + ```nginx 290 + server { 291 + listen 80; 292 + server_name yourdomain.com; 293 + 294 + location / { 295 + proxy_pass http://localhost:3000; 296 + proxy_http_version 1.1; 297 + proxy_set_header Upgrade $http_upgrade; 298 + proxy_set_header Connection 'upgrade'; 299 + proxy_set_header Host $host; 300 + proxy_cache_bypass $http_upgrade; 301 + proxy_set_header X-Real-IP $remote_addr; 302 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 303 + proxy_set_header X-Forwarded-Proto $scheme; 304 + } 305 + } 306 + ``` 307 + 308 + Enable site and get SSL: 309 + 310 + ```bash 311 + sudo ln -s /etc/nginx/sites-available/markedit /etc/nginx/sites-enabled/ 312 + sudo nginx -t 313 + sudo systemctl reload nginx 314 + sudo certbot --nginx -d yourdomain.com 315 + ``` 316 + 317 + ### Option 2: Docker on Cloud Platform 318 + 319 + #### AWS ECS / Google Cloud Run / Azure Container Instances 320 + 321 + 1. Build and push images to container registry: 322 + 323 + ```bash 324 + # Tag images 325 + docker tag markedit-backend:latest your-registry/markedit-backend:latest 326 + docker tag markedit-frontend:latest your-registry/markedit-frontend:latest 327 + 328 + # Push images 329 + docker push your-registry/markedit-backend:latest 330 + docker push your-registry/markedit-frontend:latest 331 + ``` 332 + 333 + 2. Deploy using platform-specific tools 334 + 3. Configure environment variables in platform dashboard 335 + 4. Set up load balancer and SSL certificate 336 + 337 + ### Option 3: Manual Deployment 338 + 339 + #### Backend 340 + 341 + ```bash 342 + # Build binary 343 + cd backend 344 + go build -o markedit-server cmd/server/main.go 345 + 346 + # Run with systemd 347 + sudo cp markedit-server /usr/local/bin/ 348 + sudo nano /etc/systemd/system/markedit.service 349 + ``` 350 + 351 + Create systemd service: 352 + 353 + ```ini 354 + [Unit] 355 + Description=MarkEdit Server 356 + After=network.target 357 + 358 + [Service] 359 + Type=simple 360 + User=www-data 361 + WorkingDirectory=/var/www/markedit 362 + EnvironmentFile=/var/www/markedit/.env 363 + ExecStart=/usr/local/bin/markedit-server 364 + Restart=always 365 + 366 + [Install] 367 + WantedBy=multi-user.target 368 + ``` 369 + 370 + Enable and start: 371 + 372 + ```bash 373 + sudo systemctl enable markedit 374 + sudo systemctl start markedit 375 + ``` 376 + 377 + #### Frontend 378 + 379 + ```bash 380 + cd frontend 381 + bun install 382 + bun run build 383 + 384 + # Serve with nginx 385 + sudo cp -r dist/* /var/www/html/ 386 + ``` 387 + 388 + ## Configuration Reference 389 + 390 + ### Backend Environment Variables 391 + 392 + | Variable | Required | Default | Description | 393 + |----------|----------|---------|-------------| 394 + | `GITHUB_CLIENT_ID` | Yes | - | GitHub OAuth client ID | 395 + | `GITHUB_CLIENT_SECRET` | Yes | - | GitHub OAuth client secret | 396 + | `SESSION_SECRET` | Yes | - | 32+ character random string for session encryption | 397 + | `DATABASE_PATH` | No | `./data/markedit.db` | Path to SQLite database file | 398 + | `GIT_CACHE_DIR` | No | `./data/repos` | Directory for git repository cache | 399 + | `CORS_ALLOWED_ORIGINS` | No | `http://localhost:4321` | Comma-separated allowed origins | 400 + | `PORT` | No | `8080` | Server port | 401 + 402 + ### Frontend Environment Variables 403 + 404 + | Variable | Required | Default | Description | 405 + |----------|----------|---------|-------------| 406 + | `PUBLIC_API_URL` | Yes | - | Backend API URL (e.g., `http://localhost:8080`) | 407 + 408 + ### Docker Compose Ports 409 + 410 + | Service | Internal Port | External Port | Description | 411 + |---------|---------------|---------------|-------------| 412 + | Backend | 8080 | 8080 | API server | 413 + | Frontend | 80 | 3000 | Web interface | 414 + 415 + ## Troubleshooting 416 + 417 + ### GitHub OAuth Issues 418 + 419 + **Problem**: "OAuth callback error" or "Invalid client" 420 + 421 + **Solution**: 422 + 1. Verify `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` are correct 423 + 2. Check callback URL in GitHub OAuth app matches your backend URL 424 + 3. Ensure callback URL format: `http://localhost:8080/auth/github/callback` 425 + 426 + ### Database Issues 427 + 428 + **Problem**: "Unable to open database" 429 + 430 + **Solution**: 431 + 1. Ensure data directory exists: `mkdir -p data` 432 + 2. Check file permissions: `chmod 755 data` 433 + 3. Verify `DATABASE_PATH` environment variable 434 + 435 + ### Git Operations Fail 436 + 437 + **Problem**: "Failed to clone repository" or "Authentication failed" 438 + 439 + **Solution**: 440 + 1. User must have granted repository access during OAuth 441 + 2. Check GitHub token has correct scopes (repo) 442 + 3. Verify `GIT_CACHE_DIR` is writable 443 + 444 + ### CORS Issues 445 + 446 + **Problem**: "CORS policy: No 'Access-Control-Allow-Origin' header" 447 + 448 + **Solution**: 449 + 1. Add frontend URL to `CORS_ALLOWED_ORIGINS` 450 + 2. Format: `http://localhost:4321,https://yourdomain.com` 451 + 3. Restart backend server 452 + 453 + ### Docker Build Fails 454 + 455 + **Problem**: Build errors or dependency issues 456 + 457 + **Solution**: 458 + ```bash 459 + # Clean Docker cache 460 + docker-compose down -v 461 + docker system prune -a 462 + 463 + # Rebuild from scratch 464 + make build 465 + make up 466 + ``` 467 + 468 + ### Frontend Can't Connect to Backend 469 + 470 + **Problem**: "Network error" or "Failed to fetch" 471 + 472 + **Solution**: 473 + 1. Verify backend is running: `curl http://localhost:8080/health` 474 + 2. Check `PUBLIC_API_URL` in frontend `.env` 475 + 3. Ensure CORS is configured correctly 476 + 4. Check browser console for specific errors 477 + 478 + ### Port Already in Use 479 + 480 + **Problem**: "Address already in use" 481 + 482 + **Solution**: 483 + ```bash 484 + # Find process using port 485 + lsof -i :8080 # or :3000 486 + 487 + # Kill process 488 + kill -9 <PID> 489 + 490 + # Or change port in configuration 491 + ``` 492 + 493 + ## Security Best Practices 494 + 495 + 1. **Never commit secrets**: Keep `.env` files out of version control 496 + 2. **Use strong SESSION_SECRET**: Minimum 32 random characters 497 + 3. **Enable HTTPS**: Use SSL certificates in production 498 + 4. **Restrict CORS**: Only allow trusted domains 499 + 5. **Regular updates**: Keep dependencies up to date 500 + 6. **Backup database**: Regularly backup `markedit.db` 501 + 7. **Monitor logs**: Check for unauthorized access attempts 502 + 503 + ## Backup and Restore 504 + 505 + ### Backup 506 + 507 + ```bash 508 + # Database 509 + cp data/markedit.db backups/markedit-$(date +%Y%m%d).db 510 + 511 + # Git cache (optional) 512 + tar -czf backups/repos-$(date +%Y%m%d).tar.gz data/repos/ 513 + ``` 514 + 515 + ### Restore 516 + 517 + ```bash 518 + # Database 519 + cp backups/markedit-20240101.db data/markedit.db 520 + 521 + # Restart services 522 + docker-compose restart 523 + ``` 524 + 525 + ## Support 526 + 527 + - **Documentation**: [README.md](./README.md) 528 + - **Issues**: [GitHub Issues](https://github.com/yourusername/markedit/issues) 529 + - **Discussions**: [GitHub Discussions](https://github.com/yourusername/markedit/discussions) 530 + 531 + ## Next Steps 532 + 533 + After setup: 534 + 1. Sign in with GitHub 535 + 2. Configure your first repository 536 + 3. Start editing markdown files 537 + 4. Create your first pull request 538 + 539 + Happy editing! 🎉
+6 -13
docker-compose.yml
··· 11 11 environment: 12 12 # Server 13 13 - PORT=8080 14 - - FRONTEND_URL=http://localhost:4321 15 - - ALLOWED_ORIGINS=http://localhost:4321,http://localhost 16 14 17 15 # GitHub OAuth (set these in .env file) 18 16 - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} 19 17 - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} 20 - - GITHUB_REDIRECT_URL=http://localhost:8080/api/auth/github/callback 21 18 22 19 # Session 23 - - SESSION_SECRET=${SESSION_SECRET:-change-me-in-production} 24 - - SESSION_SECURE=false 25 - - SESSION_MAX_AGE=86400 20 + - SESSION_SECRET=${SESSION_SECRET} 26 21 27 22 # Database 28 23 - DATABASE_PATH=/app/data/markedit.db 29 24 30 25 # Git 31 26 - GIT_CACHE_DIR=/app/data/repos 32 - - GIT_AUTHOR_NAME=MarkEdit 33 - - GIT_AUTHOR_EMAIL=markedit@users.noreply.github.com 34 27 35 - # Logging 36 - - LOG_LEVEL=info 28 + # CORS 29 + - CORS_ALLOWED_ORIGINS=http://localhost:4321,http://localhost:3000 37 30 volumes: 38 31 - markedit-data:/app/data 39 32 restart: unless-stopped ··· 42 35 interval: 30s 43 36 timeout: 3s 44 37 retries: 3 45 - start_period: 5s 38 + start_period: 10s 46 39 47 40 frontend: 48 41 build: ··· 50 43 dockerfile: Dockerfile 51 44 container_name: markedit-frontend 52 45 ports: 53 - - "4321:80" 46 + - "3000:80" 54 47 environment: 55 48 - PUBLIC_API_URL=http://localhost:8080 56 49 depends_on: 57 50 - backend 58 51 restart: unless-stopped 59 52 healthcheck: 60 - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"] 53 + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/"] 61 54 interval: 30s 62 55 timeout: 3s 63 56 retries: 3
+3
frontend/bun.lock
··· 29 29 "remark": "^15.0.1", 30 30 "remark-parse": "^11.0.0", 31 31 "remark-stringify": "^11.0.0", 32 + "sonner": "^2.0.7", 32 33 "tailwind-merge": "^3.4.0", 33 34 "tailwindcss": "^3.4.0", 34 35 "turndown": "^7.2.2", ··· 988 989 "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], 989 990 990 991 "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], 992 + 993 + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], 991 994 992 995 "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 993 996
+1
frontend/package.json
··· 34 34 "remark": "^15.0.1", 35 35 "remark-parse": "^11.0.0", 36 36 "remark-stringify": "^11.0.0", 37 + "sonner": "^2.0.7", 37 38 "tailwind-merge": "^3.4.0", 38 39 "tailwindcss": "^3.4.0", 39 40 "turndown": "^7.2.2",
+35 -13
frontend/src/components/dashboard/DashboardApp.tsx
··· 1 1 import { useState } from 'react'; 2 2 import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 3 + import { Toaster } from 'sonner'; 3 4 import { Header } from '../layout/Header'; 4 5 import { SetupWizard } from './SetupWizard'; 5 6 import { FileTree } from './FileTree'; 6 7 import { EditorContainer } from '../editor/EditorContainer'; 7 8 import { useFiles } from '../../lib/hooks/useRepos'; 8 9 import { useCurrentUser } from '../../lib/hooks/useAuth'; 10 + import { Loading } from '../ui/Loading'; 11 + import { EmptyState } from '../ui/EmptyState'; 9 12 10 13 const queryClient = new QueryClient({ 11 14 defaultOptions: { ··· 19 22 export function DashboardApp() { 20 23 return ( 21 24 <QueryClientProvider client={queryClient}> 25 + <Toaster 26 + position="top-right" 27 + toastOptions={{ 28 + style: { 29 + border: '2px solid black', 30 + fontFamily: 'Crimson Pro, serif', 31 + }, 32 + className: 'toast', 33 + }} 34 + /> 22 35 <Dashboard /> 23 36 </QueryClientProvider> 24 37 ); ··· 44 57 if (userLoading) { 45 58 return ( 46 59 <div className="min-h-screen bg-gray-50 flex items-center justify-center"> 47 - <div className="text-center"> 48 - <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto mb-4"></div> 49 - <div className="text-gray-600">Loading...</div> 50 - </div> 60 + <Loading size="lg" text="Loading your dashboard..." /> 51 61 </div> 52 62 ); 53 63 } ··· 101 111 102 112 {filesLoading ? ( 103 113 <div className="p-4"> 104 - <div className="animate-pulse space-y-2"> 105 - <div className="h-8 bg-gray-200 rounded"></div> 106 - <div className="h-8 bg-gray-200 rounded"></div> 107 - <div className="h-8 bg-gray-200 rounded"></div> 108 - </div> 114 + <Loading text="Loading files..." /> 109 115 </div> 110 - ) : filesData?.files ? ( 116 + ) : filesData?.files && filesData.files.length > 0 ? ( 111 117 <FileTree 112 118 files={filesData.files} 113 119 selectedFile={selectedFile} ··· 115 121 /> 116 122 ) : ( 117 123 <div className="p-4"> 118 - <div className="text-sm text-gray-600"> 119 - No markdown files found in this repository. 120 - </div> 124 + <EmptyState 125 + icon={ 126 + <svg 127 + className="w-16 h-16" 128 + fill="none" 129 + viewBox="0 0 24 24" 130 + stroke="currentColor" 131 + > 132 + <path 133 + strokeLinecap="round" 134 + strokeLinejoin="round" 135 + strokeWidth={1.5} 136 + d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" 137 + /> 138 + </svg> 139 + } 140 + title="No Files Found" 141 + description={`No markdown files found in ${repoConfig.folder || 'this repository'}. Add .md or .mdx files to get started.`} 142 + /> 121 143 </div> 122 144 )} 123 145 </aside>
+4
frontend/src/components/dashboard/SetupWizard.tsx
··· 1 1 import { useState, useEffect } from 'react'; 2 + import { toast } from 'sonner'; 2 3 import { Button } from '../ui/button'; 3 4 import { useRepositories } from '../../lib/hooks/useRepos'; 4 5 import { apiClient } from '../../lib/api'; ··· 53 54 setFolders(dirs); 54 55 } catch (error) { 55 56 console.error('Failed to fetch folders:', error); 57 + toast.error('Failed to load folders', { 58 + description: error instanceof Error ? error.message : 'Please try again', 59 + }); 56 60 setFolders([]); 57 61 } finally { 58 62 setLoadingFolders(false);
+12 -5
frontend/src/components/editor/EditorContainer.tsx
··· 1 1 import { useState, useEffect, useCallback, useRef } from 'react'; 2 + import { toast } from 'sonner'; 2 3 import { TipTapEditor } from './TipTapEditor'; 3 4 import { FrontmatterEditor } from './FrontmatterEditor'; 4 5 import { PublishDialog } from './PublishDialog'; ··· 6 7 import { useFileContent, useUpdateFile } from '../../lib/hooks/useFileContent'; 7 8 import { useBranchStatus, usePublish } from '../../lib/hooks/useBranch'; 8 9 import { debounce } from '../../lib/utils/debounce'; 10 + import { Loading } from '../ui/Loading'; 9 11 import type { PublishResponse } from '../../lib/api/branch'; 10 12 11 13 interface EditorContainerProps { ··· 63 65 setHasUnsavedChanges(false); 64 66 } catch (error) { 65 67 console.error('Failed to save:', error); 68 + toast.error('Failed to save changes', { 69 + description: error instanceof Error ? error.message : 'Please try again', 70 + }); 66 71 } finally { 67 72 setIsSaving(false); 68 73 } ··· 119 124 setShowPublishDialog(false); 120 125 setShowSuccessDialog(true); 121 126 setHasUnsavedChanges(false); 127 + toast.success('Pull request created successfully!', { 128 + description: `Branch: ${result.branch_name}`, 129 + }); 122 130 } catch (error) { 123 131 console.error('Failed to publish:', error); 124 - alert('Failed to publish changes. Please try again.'); 132 + toast.error('Failed to publish changes', { 133 + description: error instanceof Error ? error.message : 'Please try again', 134 + }); 125 135 } 126 136 }; 127 137 128 138 if (isLoading) { 129 139 return ( 130 140 <div className="flex items-center justify-center h-full"> 131 - <div className="text-center"> 132 - <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto mb-4"></div> 133 - <div className="text-gray-600">Loading file...</div> 134 - </div> 141 + <Loading size="lg" text="Loading file..." /> 135 142 </div> 136 143 ); 137 144 }
+44
frontend/src/components/ui/EmptyState.tsx
··· 1 + import React from 'react'; 2 + 3 + interface EmptyStateProps { 4 + icon?: React.ReactNode; 5 + title: string; 6 + description?: string; 7 + action?: { 8 + label: string; 9 + onClick: () => void; 10 + }; 11 + } 12 + 13 + export const EmptyState: React.FC<EmptyStateProps> = ({ 14 + icon, 15 + title, 16 + description, 17 + action, 18 + }) => { 19 + return ( 20 + <div className="flex flex-col items-center justify-center py-12 px-4 text-center"> 21 + {icon && ( 22 + <div className="mb-4 text-gray-400"> 23 + {icon} 24 + </div> 25 + )} 26 + <h3 className="text-xl font-bold text-gray-900 mb-2 font-heading"> 27 + {title} 28 + </h3> 29 + {description && ( 30 + <p className="text-gray-600 max-w-md mb-6"> 31 + {description} 32 + </p> 33 + )} 34 + {action && ( 35 + <button 36 + onClick={action.onClick} 37 + className="px-6 py-2 bg-amber-600 text-white font-bold border-2 border-black hover:bg-amber-700 transition-colors" 38 + > 39 + {action.label} 40 + </button> 41 + )} 42 + </div> 43 + ); 44 + };
+53
frontend/src/components/ui/Loading.tsx
··· 1 + import React from 'react'; 2 + 3 + interface LoadingProps { 4 + size?: 'sm' | 'md' | 'lg'; 5 + text?: string; 6 + } 7 + 8 + export const Loading: React.FC<LoadingProps> = ({ size = 'md', text }) => { 9 + const sizeClasses = { 10 + sm: 'w-4 h-4 border-2', 11 + md: 'w-8 h-8 border-3', 12 + lg: 'w-12 h-12 border-4', 13 + }; 14 + 15 + return ( 16 + <div className="flex flex-col items-center justify-center gap-3"> 17 + <div 18 + className={`${sizeClasses[size]} border-black border-t-amber-600 rounded-full animate-spin`} 19 + role="status" 20 + aria-label="Loading" 21 + /> 22 + {text && ( 23 + <p className="text-sm font-medium text-gray-700">{text}</p> 24 + )} 25 + </div> 26 + ); 27 + }; 28 + 29 + interface LoadingOverlayProps { 30 + text?: string; 31 + } 32 + 33 + export const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ text }) => { 34 + return ( 35 + <div className="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center z-50"> 36 + <Loading size="lg" text={text} /> 37 + </div> 38 + ); 39 + }; 40 + 41 + interface SkeletonProps { 42 + className?: string; 43 + } 44 + 45 + export const Skeleton: React.FC<SkeletonProps> = ({ className = '' }) => { 46 + return ( 47 + <div 48 + className={`animate-pulse bg-gray-200 border-2 border-black ${className}`} 49 + role="status" 50 + aria-label="Loading content" 51 + /> 52 + ); 53 + };