mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 20:59:41 +02:00
320 lines
8.7 KiB
TypeScript
320 lines
8.7 KiB
TypeScript
import React, { useCallback, useEffect } from 'react';
|
|
import { View, SafeAreaView, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
|
import { Stack, useLocalSearchParams } from 'expo-router';
|
|
import { useTheme } from '~/utils/theme/theme';
|
|
import { useDocumentEditor } from '~/hooks/useDocumentEditor';
|
|
import { DocumentContent } from './DocumentContent';
|
|
import { DocumentToolbar, KeyboardShortcutsInfo } from './DocumentToolbar';
|
|
import { DocumentTagsEditor } from './DocumentTagsEditor';
|
|
import { DocumentHeader } from './DocumentHeader';
|
|
import { VariantCreator } from '~/components/variants/VariantCreator';
|
|
import { BottomLLMToolbar } from '~/components/ai/BottomLLMToolbar';
|
|
import { Text } from '~/components/ui/Text';
|
|
import { Skeleton } from '~/components/ui/Skeleton';
|
|
import { EDITOR_CONFIG } from '~/config/editorConfig';
|
|
|
|
export interface DocumentEditorProps {
|
|
spaceId: string;
|
|
documentId: string;
|
|
}
|
|
|
|
/**
|
|
* Optimierter Dokumenten-Editor mit separaten Komponenten und Custom Hooks
|
|
* Ersetzt die ursprüngliche 1.322-Zeilen-Komponente
|
|
*/
|
|
export const DocumentEditor: React.FC<DocumentEditorProps> = ({ spaceId, documentId }) => {
|
|
const { isDark } = useTheme();
|
|
const params = useLocalSearchParams();
|
|
const initialMode = (params.mode as 'edit' | 'preview') || 'edit';
|
|
|
|
const {
|
|
state,
|
|
dispatch,
|
|
saveDocument,
|
|
toggleMode,
|
|
updateContent,
|
|
updateTitle,
|
|
updateTags,
|
|
autoSave,
|
|
navigateToNextDocument,
|
|
navigateToSpace,
|
|
isNewDocument,
|
|
canSave,
|
|
} = useDocumentEditor({
|
|
spaceId,
|
|
documentId,
|
|
initialMode,
|
|
});
|
|
|
|
// Keyboard Shortcuts (nur für Web)
|
|
useEffect(() => {
|
|
if (Platform.OS !== 'web') return;
|
|
|
|
const handleKeyPress = (e: KeyboardEvent) => {
|
|
if (e.ctrlKey || e.metaKey) {
|
|
switch (e.key) {
|
|
case 's':
|
|
e.preventDefault();
|
|
if (canSave) {
|
|
saveDocument();
|
|
}
|
|
break;
|
|
case 'p':
|
|
e.preventDefault();
|
|
toggleMode();
|
|
break;
|
|
case 'k':
|
|
e.preventDefault();
|
|
// Focus auf Content-Eingabe setzen
|
|
dispatch({ type: 'SET_MODE', payload: 'edit' });
|
|
break;
|
|
case 'n':
|
|
e.preventDefault();
|
|
navigateToSpace();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleKeyPress);
|
|
return () => document.removeEventListener('keydown', handleKeyPress);
|
|
}, [canSave, saveDocument, toggleMode, dispatch, navigateToSpace]);
|
|
|
|
// Handlers
|
|
const handleToggleMode = useCallback(() => {
|
|
toggleMode();
|
|
}, [toggleMode]);
|
|
|
|
const handleSave = useCallback(() => {
|
|
saveDocument();
|
|
}, [saveDocument]);
|
|
|
|
const handleShowTags = useCallback(() => {
|
|
dispatch({ type: 'SET_SHOW_TAGS_EDITOR', payload: !state.showTagsEditor });
|
|
}, [dispatch, state.showTagsEditor]);
|
|
|
|
const handleShowVariantCreator = useCallback(() => {
|
|
dispatch({ type: 'SET_SHOW_VARIANT_CREATOR', payload: !state.showVariantCreator });
|
|
}, [dispatch, state.showVariantCreator]);
|
|
|
|
const handleTagsUpdate = useCallback(
|
|
(tags: string[]) => {
|
|
updateTags(tags);
|
|
},
|
|
[updateTags]
|
|
);
|
|
|
|
const handleContentChange = useCallback(
|
|
(content: string) => {
|
|
updateContent(content);
|
|
},
|
|
[updateContent]
|
|
);
|
|
|
|
const handleTitleChange = useCallback(
|
|
(title: string) => {
|
|
updateTitle(title);
|
|
},
|
|
[updateTitle]
|
|
);
|
|
|
|
const handleGenerateText = useCallback(
|
|
(text: string) => {
|
|
// Füge generierten Text am Ende des aktuellen Inhalts hinzu
|
|
const newContent = state.content + '\\n\\n---\\n\\n' + text;
|
|
updateContent(newContent);
|
|
},
|
|
[state.content, updateContent]
|
|
);
|
|
|
|
const handleCreateVariant = useCallback(
|
|
(variant: any) => {
|
|
// Hier würde die Varianten-Erstellung implementiert
|
|
console.log('Create variant:', variant);
|
|
dispatch({ type: 'SET_SHOW_VARIANT_CREATOR', payload: false });
|
|
},
|
|
[dispatch]
|
|
);
|
|
|
|
// Loading Screen
|
|
if (state.loading) {
|
|
return (
|
|
<SafeAreaView style={{ flex: 1, backgroundColor: isDark ? '#111827' : '#f9fafb' }}>
|
|
<Stack.Screen
|
|
options={{
|
|
title: 'Lädt...',
|
|
headerStyle: { backgroundColor: isDark ? '#1f2937' : '#ffffff' },
|
|
headerTintColor: isDark ? '#f9fafb' : '#1f2937',
|
|
}}
|
|
/>
|
|
<View style={{ flex: 1, padding: 16 }}>
|
|
<Skeleton width="100%" height={32} style={{ marginBottom: 16 }} />
|
|
<Skeleton width="80%" height={20} style={{ marginBottom: 24 }} />
|
|
<Skeleton width="100%" height={200} style={{ marginBottom: 16 }} />
|
|
<Skeleton width="60%" height={20} style={{ marginBottom: 16 }} />
|
|
<Skeleton width="90%" height={20} style={{ marginBottom: 16 }} />
|
|
<Skeleton width="75%" height={20} />
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
// Error Screen
|
|
if (state.error) {
|
|
return (
|
|
<SafeAreaView style={{ flex: 1, backgroundColor: isDark ? '#111827' : '#f9fafb' }}>
|
|
<Stack.Screen
|
|
options={{
|
|
title: 'Fehler',
|
|
headerStyle: { backgroundColor: isDark ? '#1f2937' : '#ffffff' },
|
|
headerTintColor: isDark ? '#f9fafb' : '#1f2937',
|
|
}}
|
|
/>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 16,
|
|
}}
|
|
>
|
|
<Text
|
|
style={{
|
|
fontSize: 18,
|
|
color: isDark ? '#ef4444' : '#dc2626',
|
|
textAlign: 'center',
|
|
marginBottom: 16,
|
|
}}
|
|
>
|
|
{state.error}
|
|
</Text>
|
|
<Text
|
|
style={{
|
|
fontSize: 14,
|
|
color: isDark ? '#9ca3af' : '#6b7280',
|
|
textAlign: 'center',
|
|
}}
|
|
>
|
|
Bitte versuche es später erneut oder kontaktiere den Support.
|
|
</Text>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={{ flex: 1, backgroundColor: isDark ? '#111827' : '#f9fafb' }}>
|
|
<Stack.Screen
|
|
options={{
|
|
title: state.document?.title || 'Neues Dokument',
|
|
headerStyle: { backgroundColor: isDark ? '#1f2937' : '#ffffff' },
|
|
headerTintColor: isDark ? '#f9fafb' : '#1f2937',
|
|
headerBackVisible: true,
|
|
}}
|
|
/>
|
|
|
|
<KeyboardAvoidingView
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
style={{ flex: 1 }}
|
|
>
|
|
<View style={{ flex: 1 }}>
|
|
{/* Keyboard Shortcuts Info (nur Web) */}
|
|
<KeyboardShortcutsInfo />
|
|
|
|
{/* Document Header */}
|
|
<DocumentHeader
|
|
title={state.title}
|
|
onTitleChange={handleTitleChange}
|
|
spaceName={state.spaceName}
|
|
isNewDocument={isNewDocument}
|
|
onNavigateToSpace={navigateToSpace}
|
|
onNavigateToNext={state.nextDocument ? navigateToNextDocument : undefined}
|
|
nextDocumentTitle={state.nextDocument?.title}
|
|
className="mb-4"
|
|
/>
|
|
|
|
{/* Tags Editor (wenn sichtbar) */}
|
|
{state.showTagsEditor && (
|
|
<DocumentTagsEditor
|
|
documentId={documentId}
|
|
tags={state.tags}
|
|
onTagsChange={handleTagsUpdate}
|
|
/>
|
|
)}
|
|
|
|
{/* Main Content Area */}
|
|
<View style={{ flex: 1 }}>
|
|
<ScrollView
|
|
style={{ flex: 1 }}
|
|
contentContainerStyle={{ flexGrow: 1 }}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
<DocumentContent
|
|
mode={state.mode}
|
|
content={state.content}
|
|
onContentChange={handleContentChange}
|
|
isNewDocument={isNewDocument}
|
|
autoFocus={isNewDocument && state.mode === 'edit'}
|
|
className="flex-1"
|
|
spaceId={spaceId}
|
|
/>
|
|
</ScrollView>
|
|
</View>
|
|
|
|
{/* Bottom Toolbar */}
|
|
<DocumentToolbar
|
|
mode={state.mode}
|
|
onToggleMode={handleToggleMode}
|
|
onSave={handleSave}
|
|
onShowTags={handleShowTags}
|
|
onShowVariantCreator={handleShowVariantCreator}
|
|
saveStatus={autoSave.saveState}
|
|
lastSaved={autoSave.lastSaved}
|
|
saveError={autoSave.error?.message}
|
|
canSave={canSave}
|
|
isGeneratingText={state.isGeneratingText}
|
|
showTagsEditor={state.showTagsEditor}
|
|
/>
|
|
|
|
{/* AI Toolbar (nur im Edit-Modus) */}
|
|
{state.mode === 'edit' && (
|
|
<BottomLLMToolbar
|
|
documentContent={state.content}
|
|
onGenerateText={(text, mode) => {
|
|
// Handle the generated text based on mode
|
|
if (mode === 'replace') {
|
|
updateContent(text);
|
|
} else {
|
|
updateContent(state.content + text);
|
|
}
|
|
}}
|
|
isGenerating={state.isGeneratingText}
|
|
setIsGenerating={(isGenerating) => {
|
|
dispatch({ type: 'SET_IS_GENERATING_TEXT', payload: isGenerating });
|
|
}}
|
|
documentId={documentId}
|
|
/>
|
|
)}
|
|
</View>
|
|
|
|
{/* Variant Creator Modal */}
|
|
{state.showVariantCreator && (
|
|
<VariantCreator
|
|
visible={state.showVariantCreator}
|
|
onClose={() => dispatch({ type: 'SET_SHOW_VARIANT_CREATOR', payload: false })}
|
|
documentContent={state.content}
|
|
documentTitle={state.title}
|
|
documentId={documentId}
|
|
spaceId={spaceId}
|
|
onVariantCreated={(newDocumentId) => {
|
|
console.log('Variant created:', newDocumentId);
|
|
dispatch({ type: 'SET_SHOW_VARIANT_CREATOR', payload: false });
|
|
}}
|
|
/>
|
|
)}
|
|
</KeyboardAvoidingView>
|
|
</SafeAreaView>
|
|
);
|
|
};
|
|
|
|
export default DocumentEditor;
|