Skip to content

Commit

Permalink
feat: add dt-react-codemirror-editor component
Browse files Browse the repository at this point in the history
  • Loading branch information
wewoor committed Jul 10, 2020
1 parent 0485c74 commit ec63107
Show file tree
Hide file tree
Showing 13 changed files with 798 additions and 22 deletions.
29 changes: 29 additions & 0 deletions src/codemirror/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const defaultEditorOptions = {
mode: 'text/x-sql',
lint: true,
indentWithTabs: true,
smartIndent: true,
lineNumbers: true,
autofocus: false,
lineWrapping: true,
readOnly: false
};

export const propEditorOptions = { // 编辑器选项
mode: 'text/x-properties',
lint: true,
indentWithTabs: true,
smartIndent: true,
lineNumbers: true,
autofocus: false
}

export const jsonEditorOptions = { // json编辑器选项
mode: 'application/json',
lint: true,
indentWithTabs: true,
smartIndent: true,
lineNumbers: true,
autofocus: false,
matchBrackets: true
}
193 changes: 193 additions & 0 deletions src/codemirror/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import * as React from 'react'

import { defaultEditorOptions } from './config'

import CodeMirror from 'codemirror';
import './languages/log';
import './languages/simpleLog';
import 'codemirror/lib/codemirror.css'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/addon/scroll/simplescrollbars.css'
import './style.scss'
import { getLinkMark, getLogMark } from './utils'

require('codemirror/mode/sql/sql')
require('codemirror/mode/python/python')
require('codemirror/mode/javascript/javascript')
require('codemirror/mode/properties/properties')
require('codemirror/addon/display/placeholder')
require('codemirror/addon/edit/matchbrackets')
require('codemirror/addon/scroll/simplescrollbars')

type EditorEventCallback = (prevValue: string, nextValue: string, editorDoc: CodeMirror.Doc) => void;

interface CodeMirrorEditorProps {

value: string;
/**
* The cursor position
*/
cursor?: CodeMirror.Position;
/**
* Whether sync the editor value when props value changed
*/
sync?: boolean;
/**
* Editor option
*/
options?: CodeMirror.EditorConfiguration;
/**
* Always show cursor in the end.
* This option be used when streaming output, such as typing logs.
*/
cursorAlwaysInEnd?: boolean;
editorRef?: (inst: CodeMirror.Editor) => void;
cursorActivity?: EditorEventCallback;
onChange?: EditorEventCallback;
onFocus?: EditorEventCallback;
focusOut?: EditorEventCallback;

className?: string;
style?: React.CSSProperties;

}

class CodeMirrorEditor extends React.Component<CodeMirrorEditorProps, any> {
private _editorRef: HTMLTextAreaElement;
private _codeMirrorInstance: CodeMirror.Editor;

componentDidMount () {
const ele = this._editorRef;
if (!ele) return;

const {
value, onChange, onFocus, cursor, options,
focusOut, cursorActivity, editorRef
} = this.props;

const editorConfig = Object.assign({}, defaultEditorOptions, options);

this._codeMirrorInstance = CodeMirror.fromTextArea(ele, editorConfig);
const editorIns = this._codeMirrorInstance;
this.renderTextMark();

// 设置 cursor 位置
if (cursor) editorIns.setCursor(cursor)

editorIns.on('change', (doc: CodeMirror.Doc) => {
if (onChange) {
onChange(value, doc.getValue(), doc)
}
})
editorIns.on('focus', (doc: CodeMirror.Doc) => {
if (onFocus) {
onFocus(value, doc.getValue(), doc)
}
})

editorIns.on('blur', (doc: CodeMirror.Doc) => {
if (focusOut) {
focusOut(value, doc.getValue(), doc)
}
})

editorIns.on('cursorActivity', (doc: CodeMirror.Doc) => {
if (cursorActivity) {
cursorActivity(value, doc.getValue(), doc)
}
})
if (editorRef) {
editorRef(editorIns);
}
}

static fromTextArea(ele: HTMLTextAreaElement, options: CodeMirror.EditorConfiguration): any {
throw new Error("Method not implemented.");
}

// eslint-disable-next-line
UNSAFE_componentWillReceiveProps(nextProps: any) {
const editorIns = this._codeMirrorInstance;
const { value, sync, cursor, cursorAlwaysInEnd, options = {} } = nextProps;
if (options) {
editorIns.setOption('readOnly', options.readOnly)
}

if (this.props.value !== value) {
if (cursor) editorIns.setCursor(cursor)
if (sync) {

const scrollInfo = editorIns.getScrollInfo();
/**
* 判断滚动条是不是在底部
*/
const isInBottom = (scrollInfo.top + scrollInfo.clientHeight) - scrollInfo.height > -10;
console.log(isInBottom);
if (!value) {
editorIns.setValue('')
} else {
editorIns.setValue(value);
}
if (cursorAlwaysInEnd) {
editorIns.setCursor(editorIns.lineCount());
} else if (!isInBottom) {
/**
* 不在底部并且不设置自动滚到底部,则滚到原来位置
*/
editorIns.scrollTo(scrollInfo.left, scrollInfo.top)
} else if (isInBottom) {
/**
* 在底部,则自动到底部
* 需要等setValue这个动作结束之后,再获取内容的高度。
*/
Promise.resolve().then(() => {
let nowScrollInfo = editorIns.getScrollInfo();
editorIns.scrollTo(nowScrollInfo.left, nowScrollInfo.height)
})
}
}
this.renderTextMark();
}
}
renderTextMark () {
const editorIns = this._codeMirrorInstance;

const marks = editorIns.getAllMarks();
for (let mark of marks) { // 重置marks
mark.clear();
}
const value = editorIns.getValue();
const linkMarks: any = [].concat(getLinkMark(value)).concat(getLogMark(value));
for (let _i = 0; _i < linkMarks.length; _i++) {
let mark = linkMarks[_i];
editorIns.markText(
editorIns.posFromIndex(mark.start),
editorIns.posFromIndex(mark.end),
{ replacedWith: mark.node }
)
}
}

render () {
const { className, style } = this.props
let renderClass = `code-editor ${className || ''}`;
let renderStyle: React.CSSProperties = {
position: 'relative'
};

Object.assign(renderStyle, style);

return (
<div
className={renderClass}
style={renderStyle}>
<textarea
ref={(e: HTMLTextAreaElement) => { this._editorRef = e }}
defaultValue={this.props.value || ''}
/>
</div>
)
}
}

export default CodeMirrorEditor
54 changes: 54 additions & 0 deletions src/codemirror/languages/log/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'codemirror/addon/mode/simple';
const codemirror = require('codemirror')

const startRegex: any = [
{
regex: /(\[.*?\])([ \t]*)(<error>)/,
token: ['tag', null, 'error.strong'],
sol: true,
next: 'error'
},
{
regex: /(\[.*?\])([ \t]*)(<info>)/,
token: ['tag', null, 'bracket'],
sol: true,
next: 'info'
},
{
regex: /(\[.*?\])([ \t]*)(<warning>)/,
token: ['tag', null, 'comment'],
sol: true,
next: 'warning'
}
]

codemirror.defineSimpleMode('dtlog', {
start: [
...startRegex,
{
regex: /.*/,
token: 'hr'
}
],
error: [
...startRegex,
{
regex: /.*/,
token: 'error.strong'
}
],
info: [
...startRegex,
{
regex: /.*/,
token: 'bracket'
}
],
warning: [
...startRegex,
{
regex: /.*/,
token: 'comment'
}
]
});
27 changes: 27 additions & 0 deletions src/codemirror/languages/simpleLog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'codemirror/addon/mode/simple';
const codemirror = require('codemirror')

codemirror.defineSimpleMode('simpleLog', {
start: [
{
regex: /^[=]+[^=]*[=]+/,
token: 'strong'
},
{
regex: /([^\w])([A-Z][\w]*)/,
token: [null, 'string']
},
{
regex: /(^[A-Z][\w]*)/,
token: 'string'
}
// {
// regex: /([^\d])([0-9]+)/,
// token: [null, 'comment']
// },
// {
// regex: /(^[0-9]+)/,
// token: 'comment'
// }
]
});
26 changes: 26 additions & 0 deletions src/codemirror/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.CodeMirror, .CodeMirror-merge-pane{
height:inherit !important;
font-family: consolas !important;
}
.editor_custom_link{
cursor: pointer;
color:#1474f1;
text-decoration:underline;
}
.editor_custom_link:hover{
color:#04b4fa;
}
.c-editor--log__error{
color:#bb0606;
font-weight: bold;
}
.c-editor--log__info{
color:#333333;
font-weight: bold;
}
.c-editor--log__warning{
color:#ee9900;
}
.c-editor--log__success{
color:#669600;
}
Loading

0 comments on commit ec63107

Please sign in to comment.