Simplifying markdown editor

This commit is contained in:
Jesús Espino 2021-03-30 19:51:57 +02:00
parent 50fc52f871
commit 56a3160109

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState, useEffect} from 'react'
import EasyMDE from 'easymde'
import React from 'react'
import SimpleMDE from 'react-simplemde-editor'
import {Utils} from '../utils'
@ -20,45 +20,16 @@ type Props = {
onAccept?: (text: string) => void
}
type State = {
isEditing: boolean
}
const MarkdownEditor = (props: Props): JSX. Element => {
const {placeholderText, uniqueId, onFocus, onBlur, onChange} = props
class MarkdownEditor extends React.Component<Props, State> {
static defaultProps = {
text: '',
}
const [isEditing, setIsEditing] = useState(false)
const [active, setActive] = useState(false)
const [text, setText] = useState(props.text)
const [editorInstance, setEditorInstance] = useState<EasyMDE>()
get text(): string {
return this.elementRef.current!.state.value || ''
}
set text(value: string) {
this.elementRef.current!.setState({value})
}
private editorInstance?: EasyMDE
private frameRef = React.createRef<HTMLDivElement>()
private elementRef = React.createRef<SimpleMDE>()
private previewRef = React.createRef<HTMLDivElement>()
constructor(props: Props) {
super(props)
this.state = {isEditing: false}
}
shouldComponentUpdate(): boolean {
return true
}
componentDidUpdate(): void {
const newText = this.props.text || ''
if (!this.state.isEditing && this.text !== newText) {
this.text = newText
}
}
showEditor(): void {
const cm = this.editorInstance?.codemirror
const showEditor = (): void => {
const cm = editorInstance?.codemirror
if (cm) {
setTimeout(() => {
cm.refresh()
@ -68,137 +39,120 @@ class MarkdownEditor extends React.Component<Props, State> {
}, 100)
}
this.setState({isEditing: true})
setIsEditing(true)
}
hideEditor(): void {
this.editorInstance?.codemirror?.getInputField()?.blur()
this.setState({isEditing: false})
}
render(): JSX.Element {
const {text, placeholderText, uniqueId, onFocus, onBlur, onChange} = this.props
let html: string
if (text) {
html = Utils.htmlFromMarkdown(text)
} else {
html = Utils.htmlFromMarkdown(placeholderText || '')
useEffect(() => {
if (!isEditing && text !== props.text) {
setText(props.text)
}
}, [props.text])
const previewElement = (
<div
ref={this.previewRef}
className={text ? 'octo-editor-preview' : 'octo-editor-preview octo-placeholder'}
style={{display: this.state.isEditing ? 'none' : undefined}}
dangerouslySetInnerHTML={{__html: html}}
onClick={() => {
if (!this.props.readonly && !this.state.isEditing) {
this.showEditor()
}
const html: string = Utils.htmlFromMarkdown(text || placeholderText || '')
const previewElement = (
<div
className={text ? 'octo-editor-preview' : 'octo-editor-preview octo-placeholder'}
style={{display: isEditing ? 'none' : undefined}}
dangerouslySetInnerHTML={{__html: html}}
onClick={() => {
if (!props.readonly && !isEditing) {
showEditor()
}
}}
/>)
const editorElement = (
<div
className='octo-editor-activeEditor'
// Use visibility instead of display here so the editor is pre-rendered, avoiding a flash on showEditor
style={isEditing ? {} : {visibility: 'hidden', position: 'absolute', top: 0, left: 0}}
onKeyDown={(e) => {
// HACKHACK: Need to handle here instad of in CodeMirror because that breaks auto-lists
if (e.keyCode === 27 && !e.shiftKey && !(e.ctrlKey || e.metaKey) && !e.altKey) { // Esc
editorInstance?.codemirror?.getInputField()?.blur()
} else if (e.keyCode === 13 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Enter
editorInstance?.codemirror?.getInputField()?.blur()
// HACKHACK: Call onAccept after visual state change
setTimeout(() => {
Utils.log('onAccept')
props.onAccept?.(text || '')
}, 20)
}
}}
>
<SimpleMDE
id={uniqueId}
getMdeInstance={(instance) => {
setEditorInstance(instance)
// BUGBUG: This breaks auto-lists
// instance.codemirror.setOption("extraKeys", {
// "Ctrl-Enter": (cm) => {
// cm.getInputField().blur()
// }
// })
}}
/>)
value={text}
const editorElement = (
<div
className='octo-editor-activeEditor'
events={{
change: (instance: any) => {
if (isEditing) {
const newText = instance.getValue()
onChange?.(newText)
}
},
blur: (instance: any) => {
const newText = instance.getValue()
const oldText = text || ''
if (newText !== oldText && onChange) {
onChange(newText)
}
// Use visibility instead of display here so the editor is pre-rendered, avoiding a flash on showEditor
style={this.state.isEditing ? {} : {visibility: 'hidden', position: 'absolute', top: 0, left: 0}}
onKeyDown={(e) => {
// HACKHACK: Need to handle here instad of in CodeMirror because that breaks auto-lists
if (e.keyCode === 27 && !e.shiftKey && !(e.ctrlKey || e.metaKey) && !e.altKey) { // Esc
this.editorInstance?.codemirror?.getInputField()?.blur()
} else if (e.keyCode === 13 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Enter
this.editorInstance?.codemirror?.getInputField()?.blur()
setActive(false)
// HACKHACK: Call onAccept after visual state change
setTimeout(() => {
Utils.log('onAccept')
this.props.onAccept?.(this.elementRef.current!.state.value)
}, 20)
}
if (onBlur) {
onBlur(newText)
}
instance.getInputField()?.blur()
setIsEditing(false)
},
focus: () => {
setActive(true)
if (onFocus) {
onFocus()
}
},
}}
>
<SimpleMDE
id={uniqueId}
ref={this.elementRef}
getMdeInstance={(instance) => {
this.editorInstance = instance
options={{
autoDownloadFontAwesome: true,
toolbar: false,
status: false,
spellChecker: false,
minHeight: '10px',
shortcuts: {
toggleStrikethrough: 'Cmd-.',
togglePreview: null,
drawImage: null,
drawLink: null,
toggleSideBySide: null,
toggleFullScreen: null,
},
}}
/>
</div>)
// BUGBUG: This breaks auto-lists
// instance.codemirror.setOption("extraKeys", {
// "Ctrl-Enter": (cm) => {
// cm.getInputField().blur()
// }
// })
}}
value={text}
const element = (
<div className={`MarkdownEditor octo-editor ${props.className || ''} ${active ? 'active' : ''}`}>
{previewElement}
{editorElement}
</div>)
events={{
change: () => {
if (this.state.isEditing) {
const newText = this.elementRef.current!.state.value
onChange?.(newText)
}
},
blur: () => {
const newText = this.elementRef.current!.state.value
const oldText = this.props.text || ''
if (newText !== oldText && onChange) {
const newHtml = newText ? Utils.htmlFromMarkdown(newText) : Utils.htmlFromMarkdown(placeholderText || '')
this.previewRef.current!.innerHTML = newHtml
onChange(newText)
}
this.text = newText
this.frameRef.current!.classList.remove('active')
if (onBlur) {
onBlur(newText)
}
this.hideEditor()
},
focus: () => {
this.frameRef.current?.classList.add('active')
this.elementRef.current?.setState({value: this.text})
if (onFocus) {
onFocus()
}
},
}}
options={{
autoDownloadFontAwesome: true,
toolbar: false,
status: false,
spellChecker: false,
minHeight: '10px',
shortcuts: {
toggleStrikethrough: 'Cmd-.',
togglePreview: null,
drawImage: null,
drawLink: null,
toggleSideBySide: null,
toggleFullScreen: null,
},
}}
/>
</div>)
const element = (
<div
ref={this.frameRef}
className={`MarkdownEditor octo-editor ${this.props.className || ''}`}
>
{previewElement}
{editorElement}
</div>)
return element
}
return element
}
export {MarkdownEditor}