1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-27 10:32:58 +02:00
joplin/ReactNativeClient/MarkdownEditor/MarkdownEditor.js
Devon Zuegel b6d4fd16c9
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224)
* The basic editor is working! No list continuation still though

* List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists

* Supports checkboxes + attempted at setting font

* Editor font works now; now need to fix the delete (look at past state)

* Fix deletion problem

* Add ordered list handler

* Add comments

* Extract insertListLine

* End lists on enter for empty bullets

* Add MarkdownView (renders badly though)

* Save edited text from MarkdownEditor

* Cleanup

* Refactor react-native-markdown-editor/

* Rename react-native-markdown-editor/ => MarkdownEditor/

* Cleanup

* Fix preview styles; still need to fix checkbox problem

* Fix keyboard padding

* Change name back to #body_changeText

* Incorporate PR feedback from @laurent22

* wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/

* Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/

* Remove log statement

* Focus TextInput in MarkdownEditor from grandparent

* Make eslint happy

* Extract textInputRefName to shared variable

* Remove accidental #setState

* Cleanup

* Cleanup

* Run linter

* Cleanup

* Update button order

* Improve styles for config descriptions

* Allow descriptions to be added to BOOL type Setting configs

* Add editorBeta Setting

* Move FailSafe details to description text

* Update descriptionText styles

* Put the editor under the beta flag toggle

* Incorporate PR feedback from @laurent22

* Refactor Markdown editor focusing

* Cleanup

* Reorder MarkdownEditor formats

* Make applyListFormat behavior more intuitive

* Add comment

* Show MarkdownEditor with preview by default

* Show preview by default, then hide on typing

* Fix MarkdownEditor selection bug

* Cleanup

* Update Markdown button styles

* Make Markdown button colors theme-conscious

* Fix merge conflict resolution mistake

* Fix broken import

* Delete package-lock.json

* Reset package-lock.json

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 10:50:45 +00:00

163 lines
4.7 KiB
JavaScript

/**
* Inspired by https://github.com/kunall17/MarkdownEditor
*/
import React from 'react';
import {
View,
StyleSheet,
TextInput,
Platform,
KeyboardAvoidingView,
TouchableOpacity,
Image,
} from 'react-native';
import { renderFormatButtons } from './renderButtons';
import { NoteBodyViewer } from 'lib/components/note-body-viewer.js';
const styles = StyleSheet.create({
buttonContainer: {
flex: 0,
flexDirection: 'row',
},
screen: { // Wrapper around the editor and the preview
flex: 1,
flexDirection: 'column',
alignItems: 'stretch',
},
});
const MarkdownPreviewButton = (props) =>
<TouchableOpacity
onPress={props.convertMarkdown}
style={{ padding: 8, borderRightWidth: 1, borderColor: props.borderColor }}>
<Image
style={{ tintColor: props.color, padding: 8 }}
source={require('./static/visibility.png')}
resizeMode="cover"
/>
</TouchableOpacity>;
export default class MarkdownEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
text: props.value,
selection: { start: 0, end: 0 },
// Show preview by default
showPreview: props.showPreview ? props.showPreview : true,
};
this.textAreaRef = React.createRef(); // For focusing the textarea
}
textInput: TextInput;
changeText = (selection: {start: number, end: number}) => (input: string) => {
let result = input;
const cursor = selection.start;
const isOnNewline = '\n' === input.slice(cursor - 1, cursor);
const isDeletion = input.length < this.state.text.length;
if (isOnNewline && !isDeletion) {
const prevLines = input.slice(0, cursor - 1).split('\n');
const prevLine = prevLines[prevLines.length - 1];
const insertListLine = (bullet) => ([
prevLines.join('\n'), // Previous text
`\n${bullet} `, // Current line with new bullet point
input.slice(cursor, input.length), // Following text
].join(''));
const insertedEndListLine = [
// Previous text (all but last bullet line, which we remove)
prevLines.slice(0, prevLines.length - 1).join('\n') ,
'\n\n', // Two newlines to get out of the list
input.slice(cursor, input.length), // Following text
].join('');
// Add new ordered list line item
if (prevLine.startsWith('- ') && !prevLine.startsWith('- [ ')) {
// If the bullet on the previous line isn't empty, add a new bullet.
if (prevLine.trim() !== '-') {
result = insertListLine('-');
} else {
result = insertedEndListLine;
}
}
// Add new checklist line item
if ((prevLine.startsWith('- [ ] ') || prevLine.startsWith('- [x] '))) {
// If the bullet on the previous line isn't empty, add a new bullet.
if (prevLine.trim() !== '- [ ]' && prevLine.trim() !== '- [x]') {
result = insertListLine('- [ ]');
} else {
result = insertedEndListLine;
}
}
// Add new ordered list item
if (/^\d+\./.test(prevLine)) {
// If the bullet on the previous line isn't empty, add a new bullet.
const digit = Number(prevLine.match(/^\d+/)[0]);
if (prevLine.trim() !== `${digit}.`) {
result = insertListLine(`${digit + 1}.`);
} else {
result = insertedEndListLine;
}
}
}
// Hide Markdown preview on text change
this.setState({ text: result, showPreview: false });
this.props.saveText(result);
if (this.props.onMarkdownChange) this.props.onMarkdownChange(input);
};
onSelectionChange = event => {
this.setState({ selection: event.nativeEvent.selection });
};
focus = () => this.textAreaRef.current.focus()
convertMarkdown = () => this.setState({ showPreview: !this.state.showPreview })
render() {
const WrapperView = Platform.OS === 'ios' ? KeyboardAvoidingView : View;
const { Formats, markdownButton } = this.props;
const { text, selection, showPreview } = this.state;
return (
<WrapperView style={styles.screen}>
<TextInput
{...this.props}
multiline
autoCapitalize="sentences"
underlineColorAndroid="transparent"
onChangeText={this.changeText(selection)}
onSelectionChange={this.onSelectionChange}
value={text}
ref={this.textAreaRef}
selection={selection}
/>
{showPreview && <NoteBodyViewer {...this.props.noteBodyViewer} />}
<View style={styles.buttonContainer}>
<MarkdownPreviewButton
convertMarkdown={this.convertMarkdown}
borderColor={this.props.borderColor}
color={this.props.markdownButtonsColor}
/>
{renderFormatButtons(
{
color: this.props.markdownButtonsColor,
getState: () => this.state,
setState: (state, callback) => {
// Hide Markdown preview on text change
this.setState({ showPreview: false });
this.setState(state, callback);
},
},
Formats,
markdownButton,
)}
</View>
</WrapperView>
);
}
}