codemirror + react + jsonlint 打造一个json可视化编辑器,附带 jslint

5,597 阅读1分钟

背景

上一篇文章里说了使用脚本方式引入 monaco-editor。其实也可以使用 codemirror 解决同样的业务问题:用户需要配置 json 并且需要对 json 进行一些定制化的校验。

下载 codemirror 以及 jsonlint

npm i codemirror jsonlint-mod --save

效果图

image.png

业务代码

import React, {
  useEffect, useRef,
} from 'react';

import CodeMirror from 'codemirror';
import jsonlint from 'jsonlint-mod';
import PropTypes from 'prop-types';

// jsonlint 等css
import 'codemirror/addon/lint/javascript-lint';
import 'codemirror/addon/edit/closebrackets';
import 'codemirror/addon/lint/lint.css';
import 'codemirror/addon/lint/lint';
import 'codemirror/addon/lint/json-lint';

// 折叠高亮等 css
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter.js';
import 'codemirror/addon/fold/xml-fold.js';
import 'codemirror/addon/fold/indent-fold.js';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/markdown-fold.js';
import 'codemirror/addon/fold/comment-fold.js';
import 'codemirror/addon/selection/active-line';

// merge 对比的css
// import 'codemirror/addon/merge/merge';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/javascript/javascript.js';

// 主题 css
import 'codemirror/theme/cobalt.css';
import 'codemirror/theme/3024-day.css';
import 'codemirror/theme/rubyblue.css';

// 自定义样式
// import './index.scss';

window.jsonlint = jsonlint;

export default function EditPolicy(props) {
  const editorRead = useRef();
  const { policy, id, getEditorValue } = props;

  useEffect(() => {
    const codeReadContainer = document.querySelector(`#jsoneditor-read-component-${id}`);
    const ctrlQ = 'ctrl + Q';
    editorRead.current = CodeMirror.fromTextArea(codeReadContainer, {
      extraKeys: {
        Ctrl: 'autocomplete',
        [ctrlQ]: cm => cm.foldCode(cm.getCursor()),
      },
      gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
      lint: true,
      line: true,
      indentUnit: 2,
      abSize: 2,
      autoCloseBrackets: true,
      mode: 'application/json',
      lineNumbers: true,
      readOnly: false,
      theme: 'cobalt',
      styleActiveLine: true,
      lineWrapping: true,
      foldGutter: true,
      foldOptions: {
        widget: (from, to) => {
          let count = '';

          let startToken = '{';
          let endToken = '}';
          const prevLine = editorRead.current.getLine(from.line);
          if (prevLine.lastIndexOf('[') > prevLine.lastIndexOf('{')) {
            startToken = '[';
            endToken = ']';
          }

          // Get json content
          const internal = editorRead.current.getRange(from, to);
          const toParse = startToken + internal + endToken;
          // Get key count
          try {
            const parsed = JSON.parse(toParse);
            count = Object.keys(parsed).length;
          } catch (e) {
            //
          }
          return count ? `\u21A4${count}\u21A6` : '\u2194';
        },
      },
    });
    editorRead.current.setSize('auto', '550px');
  }, [id]);

  useEffect(() => {
    editorRead.current.on('change', (codemirror) => {
      if (getEditorValue) {
        getEditorValue(codemirror.getValue());
      }
    });
  }, [getEditorValue]);

  useEffect(() => {
    editorRead.current.setValue(policy);
  }, [policy]);
  return <textarea id={`jsoneditor-read-component-${id}`} />;
}

EditPolicy.propTypes = {
  policy: PropTypes.string.isRequired,
  id: PropTypes.string.isRequired,
  getEditorValue: PropTypes.func,
};

EditPolicy.defaultProps = {
  getEditorValue: () => {},
};