import React, { FC, ReactElement, useState, useRef, useCallback, useEffect, memo } from 'react' import { MarkdownEditContainer } from './style' import NavBar from './navbar' import md from './markdown' import { Spin } from 'antd'; import { IHistory, IKeyCodeMap, IProps } from './type' import { getCursorPosition, setSelectionRange, addList, cancelTabSpace, handleText, clickEnter, autoComplementBracket } from './utils' import 'antd/dist/antd.css'; let scrolling: 0 | 1 | 2 = 0; let scrollTimer: any; let renderTimer: any; let history: IHistory = { value: '', pre: null, next: null, selectionStart: 0, selectionEnd: 0 } let historyTimer: any; const MarkdownEditor: FC = ({ initialValue = '', onChange = ()=>{}, tabSpace = 4 }): ReactElement => { const [value, setValue] = useState('') const [htmlString, setHtmlString] = useState('') const [line, setLine] = useState(['']) const [loading, setLoading] = useState(true) const [preview, setPreview] = useState(false) const lineNode = useRef(null) const editorNode = useRef(null) const showNode = useRef(null) const handleChange = useCallback((e: any): void => { onChange(e.target.value, md.render(e.target.value)) wrapSetValue(e.target.value) }, []) const haneleScroll = useCallback((e: any): void => { let { target } = e let scale = getScale(target) lineNode.current.scrollTop = target.scrollTop if(target.nodeName === 'TEXTAREA') { if(scrolling === 0) scrolling = 1; else if(scrolling === 2) return; driveScroll(scale, showNode.current) } else { if(scrolling === 0) scrolling = 2; else if(scrolling === 1) return; driveScroll(scale, editorNode.current) } }, []) const driveScroll = useCallback((scale: number, el: HTMLElement): void => { let { scrollHeight, clientHeight } = el el.scrollTop = (scrollHeight - clientHeight) * scale if(scrollTimer) clearTimeout(scrollTimer); scrollTimer = setTimeout(() => { scrolling = 0 clearTimeout(scrollTimer) }, 200) }, []) const getScale = useCallback((node: HTMLElement): number => { let { scrollHeight, scrollTop, clientHeight } = node return scrollTop / (scrollHeight - clientHeight) }, []) const handleKeyDown = useCallback((e: any): void => { let { keyCode, ctrlKey, altKey, shiftKey } = e; const el = e.target // console.log(keyCode) if(ctrlKey){ switch(keyCode){ case IKeyCodeMap.ctrlZ: if(!history.pre) return e.preventDefault(); var { value, selectionStart, selectionEnd } = history.pre; setValue(value) history = history.pre setSelectionRange(editorNode.current, selectionStart, selectionEnd) e.preventDefault() break case IKeyCodeMap.ctrlY: if(!history.next) return e.preventDefault(); var { value, selectionStart, selectionEnd } = history.next; setValue(value) history = history.next; setSelectionRange(editorNode.current, selectionStart, selectionEnd) e.preventDefault() break case IKeyCodeMap.ctrlB: handleText(editorNode.current, '**', '', wrapSetValue) e.preventDefault() break case IKeyCodeMap.ctrlI: handleText(editorNode.current, '*', '', wrapSetValue) e.preventDefault() break default: break } }else if(shiftKey){ switch(keyCode){ case IKeyCodeMap.tab: cancelTabSpace(el, tabSpace, wrapSetValue) e.preventDefault() break case IKeyCodeMap.shift9: autoComplementBracket(el, wrapSetValue, ['(', ')']) e.preventDefault() break case IKeyCodeMap.shiftArrayBracket: autoComplementBracket(el, wrapSetValue, ['{', '}']) e.preventDefault() break case IKeyCodeMap.oneMark: autoComplementBracket(el, wrapSetValue, ['\"', '\"']) e.preventDefault() break default: break } }else{ switch(keyCode){ case IKeyCodeMap.tab: addList(el, ' '.repeat(tabSpace), wrapSetValue, 2) e.preventDefault() break case IKeyCodeMap.enter: clickEnter(el, wrapSetValue, tabSpace) e.preventDefault() break case IKeyCodeMap.shiftArrayBracket: autoComplementBracket(el, wrapSetValue, ['[', ']']) e.preventDefault() break case IKeyCodeMap.oneMark: autoComplementBracket(el, wrapSetValue, ['\'', '\'']) e.preventDefault() break default: break } } }, []) const wrapSetValue = (value: string, selectionStart : null | number = null, selectionEnd : null | number = null): void => { setValue(value) setHistory(selectionStart, selectionEnd) } const setHistory = (selectionStart : null | number, selectionEnd : null | number): void => { const [start, end] = getCursorPosition(editorNode.current) if(historyTimer) clearTimeout(historyTimer); historyTimer = setTimeout(()=>{ history.next = { value: editorNode.current.value, pre: history, next: null, selectionStart: selectionStart ? selectionStart : start, selectionEnd: selectionEnd ? selectionEnd : end } history = history.next clearTimeout(historyTimer) }, 1000) } useEffect(()=>{ if(renderTimer) clearTimeout(renderTimer); setLine(editorNode.current.value.split('\n')) renderTimer = setTimeout(() => { setHtmlString(md.render(value)) clearTimeout(renderTimer) }, 200) },[value]) useEffect(()=>{ history.value = initialValue; setValue(initialValue) let len = initialValue.length; setSelectionRange(editorNode.current, len, len) }, []) return (
{ line.map((item, index) => { return (

{ index+1 }

) }) }