Merge pull request #903 from esteemapp/bugfix/markdown-editor

Fixed markdown editor bugs
This commit is contained in:
uğur erdal 2019-06-11 22:16:57 +03:00 committed by GitHub
commit 682bba49aa
7 changed files with 69 additions and 67 deletions

View File

@ -1,32 +1,40 @@
import { replaceBetween } from './utils'; import { replaceBetween } from './utils';
export default ({ getState, item, setState }) => { export default async ({ getState, item, setState }) => {
let { text } = getState(); const states = getState();
const { selection } = getState(); let { text } = states;
text = text || ''; const { selection } = states;
let newText; let newText;
let newSelection; let newSelection;
if (selection.start !== selection.end) {
text = text || '';
const isSelected = selection.start === selection.end;
const hasLineBreakOnStart = text.substring(selection.start - 1, selection.start) === '\n';
const hasLineBreakOnEnd = text.substring(selection.end - 1, selection.end) === '\n';
if (!isSelected && hasLineBreakOnStart) {
newText = replaceBetween( newText = replaceBetween(
text, text,
selection, selection,
`${item.prefix} ${text.substring(selection.start, selection.end)}\n`, `${item.prefix} ${text.substring(selection.start, selection.end)}\n`,
); );
newSelection = { start: selection.end + 3, end: selection.end + 3 }; newSelection = { start: selection.end + 3, end: selection.end + 3 };
} else if ( } else if (!isSelected && !hasLineBreakOnStart) {
selection.start === selection.end && newText = replaceBetween(
text.substring(selection.end - 1, selection.end) === '\n' text,
) { selection,
`\n${item.prefix} ${text.substring(selection.start, selection.end)}\n`,
);
newSelection = { start: selection.end + 3, end: selection.end + 3 };
} else if (isSelected && hasLineBreakOnEnd) {
newText = replaceBetween(text, selection, `${item.prefix} `); newText = replaceBetween(text, selection, `${item.prefix} `);
newSelection = { start: selection.start + 2, end: selection.start + 2 }; newSelection = { start: selection.start + 2, end: selection.start + 2 };
} else { } else if (isSelected && !hasLineBreakOnEnd) {
newText = replaceBetween(text, selection, `\n${item.prefix} `); newText = replaceBetween(text, selection, `\n${item.prefix} `);
newSelection = { start: selection.start + 3, end: selection.start + 3 }; newSelection = { start: selection.start + 3, end: selection.start + 3 };
} }
setState({ text: newText }, () => { await setState({ text: newText, textUpdated: true });
setTimeout(() => { await setState({ newSelection });
setState({ newSelection });
}, 300);
});
}; };

View File

@ -3,15 +3,15 @@ import { isStringWebLink, replaceBetween } from './utils';
export const writeUrlTextHere = 'https://example.com'; export const writeUrlTextHere = 'https://example.com';
export const writeTextHereString = 'Text here'; export const writeTextHereString = 'Text here';
export default ({ getState, item, setState, isImage = null }) => { export default async ({ getState, item, setState, isImage = null }) => {
const { selection, text } = getState(); const { selection, text } = getState();
const imagePrefix = isImage ? '!' : ''; const imagePrefix = isImage ? '!' : '';
const itemText = item ? item.text : writeTextHereString; const itemText = item ? item.text : writeTextHereString;
const itemUrl = item ? item.url : writeUrlTextHere; const itemUrl = item ? item.url : writeUrlTextHere;
let newText; let newText;
let newSelection; let newSelection;
const selectedText = text.substring(selection.start, selection.end); const selectedText = text.substring(selection.start, selection.end);
if (selection.start !== selection.end) { if (selection.start !== selection.end) {
if (isStringWebLink(selectedText)) { if (isStringWebLink(selectedText)) {
newText = replaceBetween(text, selection, `${imagePrefix}[${itemText}](${selectedText})`); newText = replaceBetween(text, selection, `${imagePrefix}[${itemText}](${selectedText})`);
@ -40,7 +40,8 @@ export default ({ getState, item, setState, isImage = null }) => {
}; };
} }
} }
setState({ text: newText }, () => {
setState({ newSelection }); await setState({ text: newText, textUpdated: true }, async () => {
await setState({ newSelection });
}); });
}; };

View File

@ -8,19 +8,14 @@ export default ({ getState, item, setState }) => {
item.wrapper.concat(text.substring(selection.start, selection.end), item.wrapper), item.wrapper.concat(text.substring(selection.start, selection.end), item.wrapper),
); );
let newPosition; let newPosition;
if (selection.start === selection.end) { if (selection.start === selection.end) {
newPosition = selection.end + item.wrapper.length; newPosition = selection.end + item.wrapper.length;
} else { } else {
newPosition = selection.end + item.wrapper.length * 2; newPosition = selection.end + item.wrapper.length * 2;
} }
const extra = { setState({ text: newText, textUpdated: true }, () => {
newSelection: { setState({ newSelection: { start: newPosition, end: newPosition } });
start: newPosition,
end: newPosition,
},
};
setState({ text: newText }, () => {
setState({ ...extra });
}); });
}; };

View File

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

View File

@ -11,7 +11,6 @@ export default [
iconType: 'FontAwesome', iconType: 'FontAwesome',
wrapper: '**', wrapper: '**',
onPress: applyWrapFormat, onPress: applyWrapFormat,
// style: { fontWeight: 'bold' },
}, },
{ {
key: 'I', key: 'I',

View File

@ -22,8 +22,13 @@ export default class MarkdownEditorView extends Component {
this.state = { this.state = {
text: props.draftBody || '', text: props.draftBody || '',
selection: { start: 0, end: 0 }, selection: { start: 0, end: 0 },
textUpdated: false,
newSelection: null, newSelection: null,
}; };
this.inputRef = React.createRef();
this.galleryRef = React.createRef();
this.clearRef = React.createRef();
} }
// Lifecycle functions // Lifecycle functions
@ -47,8 +52,8 @@ export default class MarkdownEditorView extends Component {
) { ) {
applyImageLink({ applyImageLink({
getState: this._getState, getState: this._getState,
setState: (state, callback) => { setState: async (state, callback) => {
this.setState(state, callback); await this.setState(state, callback);
}, },
item: { url: nextProps.uploadedImage.url, text: nextProps.uploadedImage.hash }, item: { url: nextProps.uploadedImage.url, text: nextProps.uploadedImage.hash },
isImage: !!nextProps.uploadedImage, isImage: !!nextProps.uploadedImage,
@ -76,6 +81,14 @@ export default class MarkdownEditorView extends Component {
// Component functions // Component functions
_changeText = input => { _changeText = input => {
const { onChange, handleOnTextChange, handleIsValid, componentID } = this.props; const { onChange, handleOnTextChange, handleIsValid, componentID } = this.props;
const { textUpdated } = this.state;
if (textUpdated) {
this.setState({
textUpdated: false,
});
return;
}
this.setState({ text: input }); this.setState({ text: input });
@ -100,14 +113,16 @@ export default class MarkdownEditorView extends Component {
selection: newSelection, selection: newSelection,
newSelection: null, newSelection: null,
}); });
} else { return;
}
this.setState({ this.setState({
selection: event.nativeEvent.selection, selection: event.nativeEvent.selection,
}); });
}
}; };
_getState = () => this.state; _getState = () => {
return this.state;
};
_renderPreview = () => { _renderPreview = () => {
const { text } = this.state; const { text } = this.state;
@ -154,7 +169,7 @@ export default class MarkdownEditorView extends Component {
onPress={() => Formats[9].onPress({ getState, setState })} onPress={() => Formats[9].onPress({ getState, setState })}
/> />
<IconButton <IconButton
onPress={() => this.ActionSheet.show()} onPress={() => this.galleryRef.current.show()}
style={styles.rightIcons} style={styles.rightIcons}
size={20} size={20}
iconStyle={styles.icon} iconStyle={styles.icon}
@ -163,21 +178,14 @@ export default class MarkdownEditorView extends Component {
/> />
<View style={styles.clearButtonWrapper}> <View style={styles.clearButtonWrapper}>
<IconButton <IconButton
onPress={() => this.ClearActionSheet.show()} onPress={() => this.clearRef.current.show()}
size={20} size={20}
iconStyle={styles.clearIcon} iconStyle={styles.clearIcon}
iconType="FontAwesome" iconType="FontAwesome"
name="trash" name="trash"
backgroundColor={styles.clearButtonWrapper.backgroundColor}
/> />
</View> </View>
{/* TODO: After alpha */}
{/* <DropdownButton
style={styles.dropdownStyle}
options={['option1', 'option2', 'option3', 'option4']}
iconName="md-more"
iconStyle={styles.dropdownIconStyle}
isHasChildIcon
/> */}
</View> </View>
</StickyBar> </StickyBar>
); );
@ -203,7 +211,7 @@ export default class MarkdownEditorView extends Component {
{!isPreviewActive ? ( {!isPreviewActive ? (
<TextInput <TextInput
multiline multiline
onChangeText={e => this._changeText(e)} onChangeText={this._changeText}
onSelectionChange={this._handleOnSelectionChange} onSelectionChange={this._handleOnSelectionChange}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder', id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
@ -214,6 +222,7 @@ export default class MarkdownEditorView extends Component {
style={styles.textWrapper} style={styles.textWrapper}
underlineColorAndroid="transparent" underlineColorAndroid="transparent"
value={text} value={text}
innerRef={this.inputRef}
/> />
) : ( ) : (
this._renderPreview() this._renderPreview()
@ -222,13 +231,12 @@ export default class MarkdownEditorView extends Component {
this._renderEditorButtons({ this._renderEditorButtons({
getState: this._getState, getState: this._getState,
setState: (state, callback) => { setState: (state, callback) => {
this.inputRef.current.focus();
this.setState(state, callback); this.setState(state, callback);
}, },
})} })}
{/* TODO: This is a problem re-factor */}
<ActionSheet <ActionSheet
ref={o => (this.ActionSheet = o)} ref={this.galleryRef}
options={[ options={[
intl.formatMessage({ intl.formatMessage({
id: 'editor.open_gallery', id: 'editor.open_gallery',
@ -246,7 +254,7 @@ export default class MarkdownEditorView extends Component {
}} }}
/> />
<ActionSheet <ActionSheet
ref={o => (this.ClearActionSheet = o)} ref={this.clearRef}
title={intl.formatMessage({ title={intl.formatMessage({
id: 'alert.clear_alert', id: 'alert.clear_alert',
})} })}
@ -259,9 +267,7 @@ export default class MarkdownEditorView extends Component {
}), }),
]} ]}
cancelButtonIndex={1} cancelButtonIndex={1}
onPress={index => { onPress={index => index === 0 && this._handleClear()}
index === 0 && this._handleClear();
}}
/> />
</KeyboardAvoidingView> </KeyboardAvoidingView>
); );

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { TextInput } from 'react-native'; import { TextInput } from 'react-native';
import { connect } from 'react-redux';
const TextInputView = props => ( const TextInputView = ({ isDarkTheme, innerRef, ...props }) => (
<TextInput keyboardAppearance={props.isDarkTheme ? 'dark' : 'light'} {...props} /> <TextInput ref={innerRef} keyboardAppearance={isDarkTheme ? 'dark' : 'light'} {...props} />
); );
const mapStateToProps = state => ({ const mapStateToProps = state => ({