memos/web/src/components/MemoEditor/services/validationService.ts
Johnny 1b11e8c841 refactor(editor): complete state machine and services migration
BREAKING CHANGE: MemoEditor internal architecture completely refactored

## Summary

Refactored MemoEditor from hooks-based state management to a three-layer
architecture (Presentation → State → Services) using useReducer pattern.

## Changes

### Architecture
- **State Layer** (5 new files): types, actions, reducer, context, barrel export
- **Service Layer** (6 new files): error, validation, upload, cache, memo services + barrel
- **Component Layer** (3 new files): EditorToolbar, EditorContent, EditorMetadata
- **Simplified Hooks** (3 new files): useMemoInit, useAutoSave, useKeyboard

### Code Reduction
- Main component: ~380 lines → ~140 lines (-63%)
- Hooks removed: 5 old hooks (useMemoEditorState, useMemoSave, etc.)
- Total lines removed: 508 lines of old code
- Utility hooks preserved: 8 hooks still in use (useLocation, useDragAndDrop, etc.)

### Improvements
- ✅ Predictable state transitions with useReducer
- ✅ Testable business logic in pure service functions
- ✅ Cleaner component code (presentation only)
- ✅ Better separation of concerns
- ✅ Type-safe actions with discriminated unions
- ✅ Centralized error handling

## Statistics
- Files changed: 26
- Commits created: 25 (squashed into 1)
- New files: 17
- Removed files: 5
- TypeScript errors: 0
- Lint errors: 0

## Testing
Manual testing required for:
- Editor functionality (create, edit, save)
- Drag and drop
- Focus mode
- Keyboard shortcuts (Cmd/Ctrl + Enter)
- Auto-save to localStorage
2025-12-23 08:38:02 +08:00

27 lines
786 B
TypeScript

import type { EditorState } from "../state";
export interface ValidationResult {
valid: boolean;
reason?: string;
}
export const validationService = {
canSave(state: EditorState): ValidationResult {
// Must have content, attachment, or local file
if (!state.content.trim() && state.metadata.attachments.length === 0 && state.localFiles.length === 0) {
return { valid: false, reason: "Content, attachment, or file required" };
}
// Cannot save while uploading
if (state.ui.isLoading.uploading) {
return { valid: false, reason: "Wait for upload to complete" };
}
// Cannot save while already saving
if (state.ui.isLoading.saving) {
return { valid: false, reason: "Save in progress" };
}
return { valid: true };
},
};