mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 15:39:40 +02:00
Restructure the context app (formerly basetext) to follow the monorepo pattern with proper workspace configuration. Changes: - Move app files to apps/context/apps/mobile/ - Rename package to @context/mobile - Update bundle ID to com.manacore.context - Create pnpm-workspace.yaml for project workspace - Add dev scripts to root package.json - Update CLAUDE.md with project documentation The app structure is prepared for future web/backend additions. Note: Existing TypeScript errors in the original codebase are preserved. These should be fixed in a follow-up PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
294 lines
7.7 KiB
TypeScript
294 lines
7.7 KiB
TypeScript
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
import { Platform } from 'react-native';
|
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
|
import { useAuth } from '~/context/AuthContext';
|
|
import {
|
|
getDocumentById,
|
|
getDocumentByShortId,
|
|
createDocument,
|
|
updateDocument,
|
|
getDocuments,
|
|
saveDocumentTags,
|
|
getSpaceById,
|
|
Document,
|
|
} from '~/services/supabaseService';
|
|
import { useAutoSave } from './useAutoSave';
|
|
import {
|
|
DocumentEditorState,
|
|
DocumentEditorAction,
|
|
initialDocumentEditorState,
|
|
documentEditorReducer,
|
|
} from '~/types/documentEditor';
|
|
import { EDITOR_CONFIG } from '~/config/editorConfig';
|
|
|
|
export interface UseDocumentEditorOptions {
|
|
spaceId: string;
|
|
documentId: string;
|
|
initialMode?: 'edit' | 'preview';
|
|
}
|
|
|
|
export interface UseDocumentEditorResult {
|
|
state: DocumentEditorState;
|
|
dispatch: React.Dispatch<DocumentEditorAction>;
|
|
|
|
// Actions
|
|
saveDocument: () => Promise<void>;
|
|
toggleMode: () => void;
|
|
updateContent: (content: string) => void;
|
|
updateTitle: (title: string) => void;
|
|
updateTags: (tags: string[]) => void;
|
|
|
|
// Auto-save
|
|
autoSave: {
|
|
saveState: 'idle' | 'saving' | 'saved' | 'error';
|
|
forceSave: () => Promise<void>;
|
|
lastSaved: Date | null;
|
|
error: Error | null;
|
|
};
|
|
|
|
// Navigation
|
|
navigateToNextDocument: () => void;
|
|
navigateToSpace: () => void;
|
|
|
|
// Utilities
|
|
isNewDocument: boolean;
|
|
canSave: boolean;
|
|
}
|
|
|
|
/**
|
|
* Custom Hook für die gesamte Dokumenten-Editor-Logik
|
|
* Ersetzt das komplexe State Management aus dem DocumentEditor Component
|
|
*/
|
|
export const useDocumentEditor = (options: UseDocumentEditorOptions): UseDocumentEditorResult => {
|
|
const { spaceId, documentId, initialMode = 'edit' } = options;
|
|
const router = useRouter();
|
|
const { user } = useAuth();
|
|
const params = useLocalSearchParams();
|
|
|
|
const [state, dispatch] = useReducer(documentEditorReducer, {
|
|
...initialDocumentEditorState,
|
|
mode: initialMode,
|
|
});
|
|
|
|
const isNewDocument = documentId === 'create';
|
|
const canSave = state.content.length >= EDITOR_CONFIG.MIN_CONTENT_LENGTH;
|
|
|
|
// Refs für Performance-Optimierung
|
|
const loadingRef = useRef(false);
|
|
const saveInProgressRef = useRef(false);
|
|
|
|
// Auto-Save Setup
|
|
const handleAutoSave = useCallback(
|
|
async (content: string) => {
|
|
if (saveInProgressRef.current) return;
|
|
|
|
try {
|
|
saveInProgressRef.current = true;
|
|
|
|
if (isNewDocument) {
|
|
// Neues Dokument erstellen
|
|
const { data, error } = await createDocument(
|
|
content,
|
|
'text', // Default type
|
|
spaceId,
|
|
{ tags: state.tags },
|
|
state.title
|
|
);
|
|
|
|
if (error) throw new Error(error);
|
|
if (data) {
|
|
dispatch({ type: 'SET_DOCUMENT', payload: data });
|
|
// Navigation zur neuen Dokument-URL
|
|
router.replace(`/spaces/${spaceId}/documents/${data.id}`);
|
|
}
|
|
} else {
|
|
// Bestehendes Dokument aktualisieren
|
|
const { success, error } = await updateDocument(documentId, {
|
|
title: state.title,
|
|
content: content,
|
|
metadata: {
|
|
...state.document?.metadata,
|
|
tags: state.tags,
|
|
},
|
|
});
|
|
|
|
if (!success) throw new Error(error);
|
|
dispatch({ type: 'SET_UNSAVED_CHANGES', payload: false });
|
|
}
|
|
} catch (error) {
|
|
console.error('Auto-save failed:', error);
|
|
throw error;
|
|
} finally {
|
|
saveInProgressRef.current = false;
|
|
}
|
|
},
|
|
[isNewDocument, spaceId, state.tags, state.title, state.document, documentId, router]
|
|
);
|
|
|
|
const autoSave = useAutoSave(state.content, {
|
|
enabled: canSave,
|
|
onSave: handleAutoSave,
|
|
onError: (error) => {
|
|
dispatch({ type: 'SET_ERROR', payload: error.message });
|
|
},
|
|
});
|
|
|
|
// Document Loading
|
|
const loadDocument = useCallback(async () => {
|
|
if (loadingRef.current) return;
|
|
|
|
try {
|
|
loadingRef.current = true;
|
|
dispatch({ type: 'SET_LOADING', payload: true });
|
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
|
|
if (isNewDocument) {
|
|
// Neues Dokument - nur Space-Daten laden
|
|
const space = await getSpaceById(spaceId);
|
|
if (space) {
|
|
dispatch({ type: 'SET_SPACE_NAME', payload: space.name });
|
|
}
|
|
|
|
// Modus aus URL-Parameter setzen
|
|
const mode = (params.mode as 'edit' | 'preview') || 'edit';
|
|
dispatch({ type: 'SET_MODE', payload: mode });
|
|
|
|
dispatch({ type: 'SET_LOADING', payload: false });
|
|
return;
|
|
}
|
|
|
|
// Bestehendes Dokument laden
|
|
let document: Document | null = null;
|
|
|
|
// Versuche zuerst mit normaler ID
|
|
document = await getDocumentById(documentId);
|
|
|
|
// Falls nicht gefunden, versuche mit Short-ID
|
|
if (!document) {
|
|
document = await getDocumentByShortId(documentId);
|
|
}
|
|
|
|
if (!document) {
|
|
dispatch({ type: 'SET_ERROR', payload: 'Dokument nicht gefunden' });
|
|
return;
|
|
}
|
|
|
|
dispatch({ type: 'SET_DOCUMENT', payload: document });
|
|
|
|
// Space-Daten laden
|
|
if (document.space_id) {
|
|
const space = await getSpaceById(document.space_id);
|
|
if (space) {
|
|
dispatch({ type: 'SET_SPACE_NAME', payload: space.name });
|
|
}
|
|
|
|
// Andere Dokumente im Space laden
|
|
const spaceDocuments = await getDocuments(document.space_id);
|
|
dispatch({ type: 'SET_SPACE_DOCUMENTS', payload: spaceDocuments });
|
|
|
|
// Nächstes Dokument für Navigation finden
|
|
const currentIndex = spaceDocuments.findIndex((doc) => doc.id === document!.id);
|
|
const nextDoc =
|
|
currentIndex >= 0 && currentIndex < spaceDocuments.length - 1
|
|
? spaceDocuments[currentIndex + 1]
|
|
: null;
|
|
dispatch({ type: 'SET_NEXT_DOCUMENT', payload: nextDoc });
|
|
}
|
|
|
|
// Modus aus URL-Parameter setzen
|
|
const mode = (params.mode as 'edit' | 'preview') || 'preview';
|
|
dispatch({ type: 'SET_MODE', payload: mode });
|
|
|
|
// Fade-in Animation
|
|
dispatch({ type: 'SET_FADE_IN', payload: true });
|
|
} catch (error) {
|
|
console.error('Failed to load document:', error);
|
|
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Laden des Dokuments' });
|
|
} finally {
|
|
dispatch({ type: 'SET_LOADING', payload: false });
|
|
loadingRef.current = false;
|
|
}
|
|
}, [documentId, spaceId, isNewDocument, params.mode]);
|
|
|
|
// Load document on mount
|
|
useEffect(() => {
|
|
if (user && spaceId && documentId) {
|
|
loadDocument();
|
|
}
|
|
}, [user, spaceId, documentId, loadDocument]);
|
|
|
|
// Actions
|
|
const saveDocument = useCallback(async () => {
|
|
if (!canSave || saveInProgressRef.current) return;
|
|
|
|
try {
|
|
await autoSave.forceSave();
|
|
} catch (error) {
|
|
console.error('Manual save failed:', error);
|
|
}
|
|
}, [canSave, autoSave]);
|
|
|
|
const toggleMode = useCallback(() => {
|
|
dispatch({ type: 'TOGGLE_MODE' });
|
|
}, []);
|
|
|
|
const updateContent = useCallback((content: string) => {
|
|
dispatch({ type: 'SET_CONTENT', payload: content });
|
|
}, []);
|
|
|
|
const updateTitle = useCallback((title: string) => {
|
|
dispatch({ type: 'SET_TITLE', payload: title });
|
|
}, []);
|
|
|
|
const updateTags = useCallback(
|
|
async (tags: string[]) => {
|
|
dispatch({ type: 'SET_TAGS', payload: tags });
|
|
|
|
// Für bestehende Dokumente sofort speichern
|
|
if (!isNewDocument && state.document) {
|
|
try {
|
|
await saveDocumentTags(state.document.id, tags);
|
|
} catch (error) {
|
|
console.error('Failed to save tags:', error);
|
|
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Speichern der Tags' });
|
|
}
|
|
}
|
|
},
|
|
[isNewDocument, state.document]
|
|
);
|
|
|
|
// Navigation
|
|
const navigateToNextDocument = useCallback(() => {
|
|
if (state.nextDocument) {
|
|
router.push(`/spaces/${spaceId}/documents/${state.nextDocument.id}`);
|
|
}
|
|
}, [state.nextDocument, spaceId, router]);
|
|
|
|
const navigateToSpace = useCallback(() => {
|
|
router.push(`/spaces/${spaceId}`);
|
|
}, [spaceId, router]);
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
// Cleanup any pending operations
|
|
saveInProgressRef.current = false;
|
|
loadingRef.current = false;
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
state,
|
|
dispatch,
|
|
saveDocument,
|
|
toggleMode,
|
|
updateContent,
|
|
updateTitle,
|
|
updateTags,
|
|
autoSave,
|
|
navigateToNextDocument,
|
|
navigateToSpace,
|
|
isNewDocument,
|
|
canSave,
|
|
};
|
|
};
|