Changed markdown component with react hooks

This commit is contained in:
Mustafa Buyukcelebi 2019-10-24 12:53:46 +03:00
parent 6d985eb1b9
commit 67797d527d
6 changed files with 213 additions and 193 deletions

View File

@ -1,9 +1,6 @@
import { replaceBetween } from './utils';
export default async ({ getState, item, setState }) => {
const states = getState();
let { text } = states;
const { selection } = states;
export default async ({ text, selection, setText, setNewSelection, item }) => {
let newText;
let newSelection;
@ -35,6 +32,6 @@ export default async ({ getState, item, setState }) => {
newSelection = { start: selection.start + 3, end: selection.start + 3 };
}
await setState({ text: newText, textUpdated: true });
await setState({ newSelection });
await setText(newText);
await setNewSelection(newSelection);
};

View File

@ -3,8 +3,7 @@ import { isStringWebLink, replaceBetween } from './utils';
export const writeUrlTextHere = 'https://example.com';
export const writeTextHereString = 'Text here';
export default async ({ getState, item, setState, isImage = null }) => {
const { selection, text } = getState();
export default async ({ text, selection, setText, setNewSelection, item, isImage = null }) => {
const imagePrefix = isImage ? '!' : '';
const itemText = item ? item.text : writeTextHereString;
const itemUrl = item ? item.url : writeUrlTextHere;
@ -41,7 +40,6 @@ export default async ({ getState, item, setState, isImage = null }) => {
}
}
await setState({ text: newText, textUpdated: true }, async () => {
await setState({ newSelection });
});
await setText(newText);
await setNewSelection(newSelection);
};

View File

@ -1,7 +1,6 @@
import { replaceBetween } from './utils';
export default ({ getState, item, setState }) => {
const { text, selection } = getState();
export default async ({ text, selection, setText, setNewSelection, item }) => {
const newText = replaceBetween(
text,
selection,
@ -15,7 +14,6 @@ export default ({ getState, item, setState }) => {
newPosition = selection.end + item.wrapper.length * 2;
}
setState({ text: newText, textUpdated: true }, () => {
setState({ newSelection: { start: newPosition, end: newPosition } });
});
await setText(newText);
await setNewSelection({ start: newPosition, end: newPosition });
};

View File

@ -1,7 +1,6 @@
import { replaceBetween } from './utils';
export default async ({ getState, item, setState }) => {
const { text, selection } = getState();
export default async ({ text, selection, setText, setNewSelection, item }) => {
let newText = replaceBetween(
text,
selection,
@ -41,8 +40,6 @@ export default async ({ getState, item, setState }) => {
)}`,
);
}
await setState({ text: newText, textUpdated: true }, async () => {
await setState({ newSelection: { start: newPosition, end: newPosition } });
});
await setText(newText);
await setNewSelection({ start: newPosition, end: newPosition });
};

View File

@ -1,9 +1,9 @@
import regexValidator from './webLinkValidator';
export const replaceBetween = (text: string, selection: Object, what: string) =>
export const replaceBetween = (text, selection, what) =>
text.substring(0, selection.start) + what + text.substring(selection.end);
export const isStringWebLink = (text: string): boolean => {
export const isStringWebLink = text => {
const pattern = regexValidator;
return pattern.test(text);
};

View File

@ -1,11 +1,11 @@
import React, { Component } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { View, KeyboardAvoidingView, FlatList, Text, Platform, ScrollView } from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import { renderPostBody } from '@esteemapp/esteem-render-helpers';
// Utils
import applyImageLink from './formats/applyWebLinkFormat';
import Formats from './formats/formats';
import applyImageLink from './formats/applyWebLinkFormat';
// Components
import { IconButton } from '../../iconButton';
@ -16,73 +16,65 @@ import { TextInput } from '../../textInput';
// Styles
import styles from './markdownEditorStyles';
export default class MarkdownEditorView extends Component {
constructor(props) {
super(props);
this.state = {
text: props.draftBody || '',
selection: { start: 0, end: 0 },
textUpdated: false,
newSelection: null,
};
const MarkdownEditorView = ({
draftBody,
handleIsFormValid,
handleOpenImagePicker,
intl,
isPreviewActive,
isReply,
isLoading,
initialFields,
onChange,
handleOnTextChange,
handleIsValid,
componentID,
uploadedImage,
}) => {
const [text, setText] = useState(draftBody || '');
const [selection, setSelection] = useState({ start: 0, end: 0 });
this.inputRef = React.createRef();
this.galleryRef = React.createRef();
this.clearRef = React.createRef();
}
const [newSelection, setNewSelection] = useState(null);
// Lifecycle functions
UNSAFE_componentWillReceiveProps(nextProps) {
const { draftBody, uploadedImage, isPreviewActive } = this.props;
if (!nextProps.isPreviewActive && isPreviewActive) {
this.setState({
selection: { start: 0, end: 0 },
});
}
if (nextProps.draftBody && draftBody !== nextProps.draftBody) {
this.setState({
text: nextProps.draftBody,
});
}
const inputRef = useRef(null);
const galleryRef = useRef(null);
const clearRef = useRef(null);
if (
nextProps.uploadedImage &&
nextProps.uploadedImage.url &&
nextProps.uploadedImage !== uploadedImage
) {
useEffect(() => {
setSelection({ start: 0, end: 0 });
}, [isPreviewActive]);
useEffect(() => {
if (uploadedImage && uploadedImage.url) {
applyImageLink({
getState: this._getState,
setState: async (state, callback) => {
await this.setState(state, callback);
},
item: { url: nextProps.uploadedImage.url, text: nextProps.uploadedImage.hash },
isImage: !!nextProps.uploadedImage,
text,
selection,
setText,
setNewSelection,
item: { url: uploadedImage.url, text: uploadedImage.hash },
isImage: !!uploadedImage,
});
}
}
}, [uploadedImage]);
componentDidUpdate(prevProps, prevState) {
const { text } = this.state;
const { handleIsFormValid } = this.props;
useEffect(() => {
setText(draftBody);
}, [draftBody]);
if (prevState.text !== text) {
const nextText = text.replace(prevState.text, '');
useEffect(() => {
const nextText = text.replace(text, '');
if (nextText && nextText.length > 0) {
this._changeText(text);
if (nextText && nextText.length > 0) {
_changeText(text);
if (handleIsFormValid) {
handleIsFormValid(text);
}
if (handleIsFormValid) {
handleIsFormValid(text);
}
}
}
}, [text]);
// Component functions
_changeText = input => {
const { onChange, handleOnTextChange, handleIsValid, componentID } = this.props;
this.setState({ text: input });
const _changeText = input => {
setText(input);
if (onChange) {
onChange(input);
@ -97,36 +89,22 @@ export default class MarkdownEditorView extends Component {
}
};
_handleOnSelectionChange = event => {
const { newSelection } = this.state;
const _handleOnSelectionChange = event => {
if (newSelection) {
this.setState({
selection: newSelection,
newSelection: null,
});
setSelection(newSelection);
setNewSelection(null);
return;
}
this.setState({
selection: event.nativeEvent.selection,
});
setSelection(event.nativeEvent.selection);
};
_getState = () => {
return this.state;
};
const _renderPreview = () => (
<ScrollView style={styles.previewContainer}>
{text ? <PostBody body={renderPostBody(text)} /> : <Text>...</Text>}
</ScrollView>
);
_renderPreview = () => {
const { text } = this.state;
return (
<ScrollView style={styles.previewContainer}>
{text ? <PostBody body={renderPostBody(text)} /> : <Text>...</Text>}
</ScrollView>
);
};
_renderMarkupButton = ({ item, getState, setState }) => (
const _renderMarkupButton = ({ item }) => (
<View style={styles.buttonWrapper}>
<IconButton
size={20}
@ -134,20 +112,18 @@ export default class MarkdownEditorView extends Component {
iconStyle={styles.icon}
iconType={item.iconType}
name={item.icon}
onPress={() => item.onPress({ getState, setState, item })}
onPress={() => item.onPress({ text, selection, setText, setNewSelection, item })}
/>
</View>
);
_renderEditorButtons = ({ getState, setState }) => (
const _renderEditorButtons = () => (
<StickyBar>
<View style={styles.leftButtonsWrapper}>
<FlatList
data={Formats}
keyboardShouldPersistTaps="always"
renderItem={({ item, index }) =>
index !== 9 && this._renderMarkupButton({ item, getState, setState })
}
renderItem={({ item, index }) => index !== 9 && _renderMarkupButton({ item })}
horizontal
/>
</View>
@ -158,10 +134,10 @@ export default class MarkdownEditorView extends Component {
iconStyle={styles.icon}
iconType="FontAwesome"
name="link"
onPress={() => Formats[9].onPress({ getState, setState })}
onPress={() => Formats[9].onPress({ text, selection, setText, setNewSelection })}
/>
<IconButton
onPress={() => this.galleryRef.current.show()}
onPress={() => galleryRef.current.show()}
style={styles.rightIcons}
size={20}
iconStyle={styles.icon}
@ -170,7 +146,7 @@ export default class MarkdownEditorView extends Component {
/>
<View style={styles.clearButtonWrapper}>
<IconButton
onPress={() => this.clearRef.current.show()}
onPress={() => clearRef.current.show()}
size={20}
iconStyle={styles.clearIcon}
iconType="FontAwesome"
@ -182,87 +158,141 @@ export default class MarkdownEditorView extends Component {
</StickyBar>
);
_handleClear = () => {
const { initialFields } = this.props;
initialFields();
this.setState({ text: '' });
const _handleClear = index => {
if (index === 0) {
initialFields();
setText('');
}
};
render() {
const { handleOpenImagePicker, intl, isPreviewActive, isReply, isLoading } = this.props;
const { text, selection } = this.state;
return (
<KeyboardAvoidingView
style={styles.container}
keyboardVerticalOffset={Platform.select({ ios: 0, android: 25 })}
behavior={Platform.OS === 'ios' ? 'padding' : null}
>
{!isPreviewActive ? (
<TextInput
multiline
onChangeText={_changeText}
onSelectionChange={_handleOnSelectionChange}
placeholder={intl.formatMessage({
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
})}
placeholderTextColor="#c1c5c7"
selection={selection}
selectionColor="#357ce6"
style={styles.textWrapper}
underlineColorAndroid="transparent"
value={text}
innerRef={inputRef}
editable={!isLoading}
/>
) : (
_renderPreview()
)}
{!isPreviewActive && _renderEditorButtons()}
<ActionSheet
ref={galleryRef}
options={[
intl.formatMessage({
id: 'editor.open_gallery',
}),
intl.formatMessage({
id: 'editor.capture_photo',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={2}
onPress={index => {
handleOpenImagePicker(index === 0 ? 'image' : index === 1 && 'camera');
}}
/>
<ActionSheet
ref={clearRef}
title={intl.formatMessage({
id: 'alert.clear_alert',
})}
options={[
intl.formatMessage({
id: 'alert.clear',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={1}
onPress={_handleClear}
/>
</KeyboardAvoidingView>
);
};
return (
<KeyboardAvoidingView
style={styles.container}
keyboardVerticalOffset={Platform.select({ ios: 0, android: 25 })}
behavior={Platform.OS === 'ios' ? 'padding' : null}
>
{!isPreviewActive ? (
<TextInput
multiline
onChangeText={this._changeText}
onSelectionChange={this._handleOnSelectionChange}
placeholder={intl.formatMessage({
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
})}
placeholderTextColor="#c1c5c7"
selection={selection}
selectionColor="#357ce6"
style={styles.textWrapper}
underlineColorAndroid="transparent"
value={text}
innerRef={this.inputRef}
editable={!isLoading}
/>
) : (
this._renderPreview()
)}
{!isPreviewActive &&
this._renderEditorButtons({
getState: this._getState,
setState: (state, callback) => {
this.inputRef.current.focus();
this.setState(state, callback);
},
})}
<ActionSheet
ref={this.galleryRef}
options={[
intl.formatMessage({
id: 'editor.open_gallery',
}),
intl.formatMessage({
id: 'editor.capture_photo',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={2}
onPress={index => {
handleOpenImagePicker(index === 0 ? 'image' : index === 1 && 'camera');
}}
/>
<ActionSheet
ref={this.clearRef}
title={intl.formatMessage({
id: 'alert.clear_alert',
})}
options={[
intl.formatMessage({
id: 'alert.clear',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={1}
onPress={index => index === 0 && this._handleClear()}
/>
</KeyboardAvoidingView>
);
}
}
export default MarkdownEditorView;
// class MarkdownEditorView extends Component {
// constructor(props) {
// super(props);
// this.state = {
// text: props.draftBody || '',
// selection: { start: 0, end: 0 },
// textUpdated: false,
// newSelection: null,
// };
// this.inputRef = React.createRef();
// this.galleryRef = React.createRef();
// this.clearRef = React.createRef();
// }
// // Lifecycle functions
// UNSAFE_componentWillReceiveProps(nextProps) {
// const { draftBody, uploadedImage, isPreviewActive } = this.props;
// if (!nextProps.isPreviewActive && isPreviewActive) {
// this.setState({
// selection: { start: 0, end: 0 },
// });
// }
// if (nextProps.draftBody && draftBody !== nextProps.draftBody) {
// this.setState({
// text: nextProps.draftBody,
// });
// }
// if (
// nextProps.uploadedImage &&
// nextProps.uploadedImage.url &&
// nextProps.uploadedImage !== uploadedImage
// ) {
// applyImageLink({
// getState: this._getState,
// setState: async (state, callback) => {
// await this.setState(state, callback);
// },
// item: { url: nextProps.uploadedImage.url, text: nextProps.uploadedImage.hash },
// isImage: !!nextProps.uploadedImage,
// });
// }
// }
// componentDidUpdate(prevProps, prevState) {
// const { text } = this.state;
// const { handleIsFormValid } = this.props;
// if (prevState.text !== text) {
// const nextText = text.replace(prevState.text, '');
// if (nextText && nextText.length > 0) {
// this._changeText(text);
// if (handleIsFormValid) {
// handleIsFormValid(text);
// }
// }
// }
// }
// // Component functions
// }