/** * 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> ); } }