mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-12-30 00:52:42 +03:00
Merge pull request #903 from esteemapp/bugfix/markdown-editor
Fixed markdown editor bugs
This commit is contained in:
commit
682bba49aa
@ -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);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -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 });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,6 @@ export default [
|
|||||||
iconType: 'FontAwesome',
|
iconType: 'FontAwesome',
|
||||||
wrapper: '**',
|
wrapper: '**',
|
||||||
onPress: applyWrapFormat,
|
onPress: applyWrapFormat,
|
||||||
// style: { fontWeight: 'bold' },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'I',
|
key: 'I',
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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 => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user