- Implement `segmentsToBlocks` utility to convert message segments into editable blocks. - Update `BlockEditor` and `useBlockEditor` to support initial block loading and improved image upload handling. - Add logic to `CreatePostScreen` to automatically switch to long-post mode when image segments are detected and preserve data when toggling modes. - Update `AboutScreen` to make the ICP filing number clickable. - Refactor mapper imports to use the updated data model paths.
122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import React, { useImperativeHandle, useCallback } from 'react';
|
|
import { View, ScrollView, StyleSheet } from 'react-native';
|
|
|
|
import { useAppColors, spacing } from '../../../theme';
|
|
import { MessageSegment } from '../../../types';
|
|
import { EditorBlock } from './blockEditorTypes';
|
|
import TextBlockInput from './TextBlockInput';
|
|
import ImageBlockView from './ImageBlockView';
|
|
import { useBlockEditor } from './useBlockEditor';
|
|
|
|
export interface BlockEditorHandle {
|
|
insertImage: () => Promise<void>;
|
|
insertCameraPhoto: () => Promise<void>;
|
|
insertTextAtCursor: (text: string) => void;
|
|
getSegments: () => MessageSegment[];
|
|
getContent: () => string;
|
|
hasUploadingImages: () => boolean;
|
|
focus: () => void;
|
|
}
|
|
|
|
interface BlockEditorProps {
|
|
placeholder?: string;
|
|
maxLength?: number;
|
|
style?: any;
|
|
textStyle?: any;
|
|
onContentChange?: (content: string) => void;
|
|
initialBlocks?: EditorBlock[];
|
|
}
|
|
|
|
const BlockEditor = React.forwardRef<BlockEditorHandle, BlockEditorProps>(
|
|
({ placeholder = '添加正文', maxLength = 2000, style, textStyle, onContentChange, initialBlocks }, ref) => {
|
|
const colors = useAppColors();
|
|
const editor = useBlockEditor(initialBlocks);
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
insertImage: editor.insertImage,
|
|
insertCameraPhoto: editor.insertCameraPhoto,
|
|
insertTextAtCursor: editor.insertTextAtCursor,
|
|
getSegments: editor.getSegments,
|
|
getContent: editor.getContent,
|
|
hasUploadingImages: editor.hasUploadingImages,
|
|
focus: () => {
|
|
const firstTextBlock = editor.blocks.find(b => b.type === 'text');
|
|
if (firstTextBlock) {
|
|
editor.focusBlock(firstTextBlock.id);
|
|
}
|
|
},
|
|
}));
|
|
|
|
const handleTextChange = useCallback(
|
|
(blockId: string, text: string, mentions: any) => {
|
|
editor.updateTextBlock(blockId, text, mentions);
|
|
onContentChange?.(editor.getContent());
|
|
},
|
|
[editor.updateTextBlock, editor.getContent, onContentChange],
|
|
);
|
|
|
|
const handleRemoveImage = useCallback(
|
|
(blockId: string) => {
|
|
editor.removeImageBlock(blockId);
|
|
onContentChange?.(editor.getContent());
|
|
},
|
|
[editor.removeImageBlock, editor.getContent, onContentChange],
|
|
);
|
|
|
|
return (
|
|
<View style={[styles.container, style]}>
|
|
<ScrollView
|
|
style={styles.scroll}
|
|
contentContainerStyle={styles.scrollContent}
|
|
showsVerticalScrollIndicator={false}
|
|
keyboardShouldPersistTaps="handled"
|
|
nestedScrollEnabled
|
|
>
|
|
{editor.blocks.map(block => {
|
|
if (block.type === 'text') {
|
|
return (
|
|
<TextBlockInput
|
|
key={block.id}
|
|
block={block}
|
|
isActive={editor.activeBlockId === block.id}
|
|
placeholder={placeholder}
|
|
maxLength={maxLength}
|
|
onTextChange={handleTextChange}
|
|
onFocusBlock={editor.setActiveBlockId}
|
|
onSelectMention={() => {}}
|
|
inputRef={(ref) => editor.registerInputRef(block.id, ref)}
|
|
style={textStyle}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ImageBlockView
|
|
key={block.id}
|
|
block={block}
|
|
onRemove={handleRemoveImage}
|
|
/>
|
|
);
|
|
})}
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
},
|
|
);
|
|
|
|
BlockEditor.displayName = 'BlockEditor';
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flexGrow: 1,
|
|
},
|
|
scroll: {
|
|
flexGrow: 1,
|
|
},
|
|
scrollContent: {
|
|
flexGrow: 1,
|
|
paddingBottom: spacing.xs,
|
|
},
|
|
});
|
|
|
|
export default BlockEditor; |