212 lines
7.3 KiB
Markdown
212 lines
7.3 KiB
Markdown
# ✏️ InkNote
|
||
|
||
A cross-platform note-taking application with rich text editing, freehand drawing, PDF annotation, and encrypted sync — inspired by Samsung Notes.
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
### Core
|
||
- **Rich Text Notes** — Bold, italic, underline, headings, bullet/numbered lists, checkboxes
|
||
- **Freehand Drawing** — Pen, highlighter, eraser with adjustable thickness and color palette (20 colors, 7 pen sizes)
|
||
- **Undo/Redo** — Full history stack for drawing operations
|
||
- **PDF Import & Annotation** — Import PDFs, overlay drawing/highlighting, persistent annotations
|
||
- **Notebooks & Folders** — Create notebooks with custom gradient covers, organize notes by folder
|
||
- **Star/Favorite** — Quick access to important notes
|
||
- **Dark & Light Mode** — Full theme support with refined color schemes
|
||
|
||
### Sync & Storage
|
||
- **Offline-First** — Works fully offline, syncs when connection returns
|
||
- **Conflict-Aware Sync** — Vector clock–based conflict detection; notifies user of merge conflicts
|
||
- **WebSocket Real-Time Push** — Changes appear on other devices instantly
|
||
- **Encrypted at Rest** — AES-256-CBC encryption for all note content on the server
|
||
- **JWT Authentication** — Secure user accounts with bcrypt password hashing
|
||
|
||
### Architecture
|
||
- **Self-Hosted Backend** — Node.js + Express + SQLite (or PostgreSQL)
|
||
- **Docker Ready** — docker-compose up and you're running
|
||
- **Client Sync Engine** — Portable JS library for any frontend (React Native, Flutter JS bridge, Electron, web)
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
inknote/
|
||
├── note-app.jsx # Full React UI (artifact / web)
|
||
├── docker-compose.yml # Container orchestration
|
||
├── backend/
|
||
│ ├── server.js # Express API + WebSocket server
|
||
│ ├── sync-client.js # Client-side sync engine library
|
||
│ ├── package.json # Node dependencies
|
||
│ ├── Dockerfile # Server container
|
||
│ └── .env.example # Environment variable template
|
||
└── README.md # This file
|
||
```
|
||
|
||
---
|
||
|
||
## Quick Start
|
||
|
||
### 1. Run the UI (Web / Artifact)
|
||
|
||
The `note-app.jsx` file is a self-contained React component. It runs directly as a Claude artifact, or you can embed it in any React project:
|
||
|
||
```jsx
|
||
import NoteApp from './note-app';
|
||
|
||
function App() {
|
||
return <NoteApp />;
|
||
}
|
||
```
|
||
|
||
### 2. Run the Backend
|
||
|
||
```bash
|
||
cd backend
|
||
cp .env.example .env # Edit secrets!
|
||
npm install
|
||
npm start # → http://localhost:4000
|
||
```
|
||
|
||
### 3. Docker Deployment
|
||
|
||
```bash
|
||
# Generate a secure encryption key
|
||
export ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||
export JWT_SECRET=$(openssl rand -base64 32)
|
||
|
||
docker-compose up -d
|
||
```
|
||
|
||
The server runs on port `4000`, the web UI on port `3000`.
|
||
|
||
---
|
||
|
||
## API Reference
|
||
|
||
### Authentication
|
||
|
||
| Method | Endpoint | Body | Response |
|
||
|--------|-----------------------|-------------------------------|---------------------|
|
||
| POST | `/api/auth/register` | `{email, password, displayName}` | `{token, user}` |
|
||
| POST | `/api/auth/login` | `{email, password}` | `{token, user}` |
|
||
|
||
### Notebooks
|
||
|
||
| Method | Endpoint | Description |
|
||
|--------|-----------------------|--------------------------|
|
||
| GET | `/api/notebooks` | List all notebooks |
|
||
| POST | `/api/notebooks` | Create notebook |
|
||
| PUT | `/api/notebooks/:id` | Update notebook |
|
||
| DELETE | `/api/notebooks/:id` | Soft-delete notebook |
|
||
|
||
### Notes
|
||
|
||
| Method | Endpoint | Description |
|
||
|--------|---------------------------|-------------------------------|
|
||
| GET | `/api/notes` | List all notes (metadata) |
|
||
| GET | `/api/notes/:id` | Get note with decrypted content |
|
||
| POST | `/api/notes` | Create note |
|
||
| PUT | `/api/notes/:id` | Update note (conflict-aware) |
|
||
| DELETE | `/api/notes/:id` | Soft-delete note |
|
||
| PUT | `/api/notes/:id/drawing` | Save drawing data (base64) |
|
||
| POST | `/api/notes/pdf` | Upload PDF (multipart/form) |
|
||
|
||
### Sync
|
||
|
||
| Method | Endpoint | Description |
|
||
|--------|------------- |------------------------------------------|
|
||
| POST | `/api/sync` | Bidirectional sync with conflict reports |
|
||
|
||
### WebSocket
|
||
|
||
Connect to `ws://localhost:4000/ws`, then send:
|
||
```json
|
||
{"type": "auth", "token": "JWT_TOKEN", "deviceId": "device-123"}
|
||
```
|
||
|
||
Receive real-time events: `note_created`, `note_updated`, `note_deleted`, `notebook_created`, etc.
|
||
|
||
---
|
||
|
||
## Sync Engine (Client)
|
||
|
||
```javascript
|
||
import { SyncEngine } from './sync-client';
|
||
|
||
const sync = new SyncEngine('https://your-server.com', 'device-abc');
|
||
|
||
// Authenticate
|
||
await sync.authenticate('user@example.com', 'password');
|
||
|
||
// Listen for events
|
||
sync.on('status', (status) => console.log('Sync:', status));
|
||
sync.on('conflict', (conflict) => {
|
||
// Present both versions to user, let them choose
|
||
console.log('Conflict:', conflict);
|
||
});
|
||
sync.on('remote_change', (change) => {
|
||
// Update local state with remote changes
|
||
console.log('Remote change:', change);
|
||
});
|
||
|
||
// Start syncing (every 30s + WebSocket push)
|
||
sync.start();
|
||
|
||
// CRUD operations auto-queue for sync
|
||
await sync.createNote({ type: 'text', title: 'Hello', content: '<p>World</p>' });
|
||
await sync.updateNote('note-id', { title: 'Updated' });
|
||
await sync.deleteNote('note-id');
|
||
await sync.uploadPDF(fileBlob);
|
||
await sync.saveDrawing('note-id', canvasDataUrl);
|
||
```
|
||
|
||
---
|
||
|
||
## Conflict Resolution
|
||
|
||
InkNote uses **vector clocks** to detect conflicts:
|
||
|
||
1. Each device maintains a logical clock counter
|
||
2. On save, the device increments its counter and includes the full vector clock
|
||
3. The server compares clocks — if the client clock is strictly newer, the update applies
|
||
4. If clocks are concurrent (both have changes the other doesn't), a **409 Conflict** is returned
|
||
5. The client receives both versions and can present a merge UI to the user
|
||
|
||
---
|
||
|
||
## Extending to Native Platforms
|
||
|
||
### React Native
|
||
Use `note-app.jsx` components with React Native adaptations (replace HTML canvas with `react-native-canvas` or `@shopify/react-native-skia` for drawing). The sync engine works as-is.
|
||
|
||
### Flutter
|
||
Use the sync engine via a JavaScript bridge, or port the API calls to Dart. For drawing, use Flutter's `CustomPainter`. For PDF, use `flutter_pdfview`.
|
||
|
||
### Electron
|
||
Wrap the React app directly in Electron for desktop. Add native file system access for PDF import/export.
|
||
|
||
---
|
||
|
||
## Security Notes
|
||
|
||
- All note content is encrypted with AES-256-CBC before database storage
|
||
- Passwords are hashed with bcrypt (12 rounds)
|
||
- JWT tokens expire after 30 days
|
||
- HTTPS should be enforced in production (use a reverse proxy like Nginx or Pangolin)
|
||
- The encryption key should be stored securely (e.g., environment variable, secrets manager)
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
| Layer | Technology |
|
||
|-----------|-------------------------------------|
|
||
| Frontend | React (web), adaptable to RN/Flutter |
|
||
| Backend | Node.js, Express |
|
||
| Database | SQLite (dev), PostgreSQL (prod) |
|
||
| Real-time | WebSocket (ws library) |
|
||
| Auth | JWT + bcrypt |
|
||
| Encryption| AES-256-CBC |
|
||
| Container | Docker + docker-compose | |