Js代碼編輯器

在有些項(xiàng)目中有時(shí)需要讓用戶輸入一些代碼,使用戶能自主處理一些數(shù)據(jù)邏輯,但如果讓用戶直接注入代碼,會(huì)有很高的風(fēng)險(xiǎn)(惡意代碼).
因此我們可以將用戶輸入的代碼以字符串的形式傳給后端,讓后端根據(jù)所傳的代碼字符串處理數(shù)據(jù).
為了使用戶能有一個(gè)較好的代碼輸入體驗(yàn),我們可以使用monaco-editor

npm install --save monaco-editor

使用的版本

  "monaco-editor": "^0.20.0",

組件MonacoEditor.vue文件

<template>
  <div ref="editor" class="main"></div>
</template>

<script>
  import * as monaco from 'monaco-editor'
  import createSqlCompleter from './util/sql-completion'
  import createJavascriptCompleter from './util/javascript-completion'
  import registerLanguage from './util/log-language'
  const global = {}

  const getHints = model => {
    let id = model.id.substring(6)
    return (global[id] && global[id].hints) || []
  }
  monaco.languages.registerCompletionItemProvider(
    'sql',
    createSqlCompleter(getHints)
  )
  monaco.languages.registerCompletionItemProvider(
    'javascript',
    createJavascriptCompleter(getHints)
  )
  registerLanguage(monaco)
  /**
   * monaco options
   * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
   */
  export default {
    props: {
      options: {
        type: Object,
        default () {
          return {}
        }
      },
      value: {
        type: String,
        required: false
      },
      language: {
        type: String
      },
      hints: {
        type: Array,
        default () {
          return []
        }
      }
    },
    name: 'MonacoEditor',
    data () {
      return {
        editorInstance: null,
        defaultOptions: {
          theme: 'vs-dark',
          fontSize: 14
        }
      }
    },
    watch: {
      value () {
        if (this.value !== this.editorInstance.getValue()) {
          this.editorInstance.setValue(this.value)
        }
      }
    },
    mounted () {
      this.initEditor()
      global[this.editorInstance._id] = this
      window.addEventListener('resize', this.layout)
    },
    destroyed () {
      this.editorInstance.dispose()
      global[this.editorInstance._id] = null
      window.removeEventListener('resize', this.layout)
    },
    methods: {
      layout () {
        this.editorInstance.layout()
      },
      undo () {
        this.editorInstance.trigger('anyString', 'undo')
        this.onValueChange()
      },
      redo () {
        this.editorInstance.trigger('anyString', 'redo')
        this.onValueChange()
      },
      getOptions () {
        let props = { value: this.value }
        this.language !== undefined && (props.language = this.language)
        let options = Object.assign({}, this.defaultOptions, this.options, props)
        return options
      },
      onValueChange () {
        this.$emit('input', this.editorInstance.getValue())
        this.$emit('change', this.editorInstance.getValue())
      },
      initEditor () {
        this.MonacoEnvironment = {
          getWorkerUrl: function () {
            return './editor.worker.bundle.js'
          }
        }
        this.editorInstance = monaco.editor.create(
          this.$refs.editor,
          this.getOptions()
        )
        this.editorInstance.onContextMenu(e => {
          this.$emit('contextmenu', e)
        })
        this.editorInstance.onDidChangeModelContent(() => {
          this.onValueChange()
        })
        this.editorInstance.addCommand(
          monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S,
          () => {
            this.$emit('save', this.editorInstance.getValue())
          }
        )
      }
    }
  }
</script>
<style scoped>
.main /deep/ .view-lines * {
  font-family: Consolas, "Courier New", monospace !important;
}
</style>

Util/javascript-completion.js

/* eslint-disable */
 import * as monaco from 'monaco-editor'
// js 有內(nèi)置提示
function createCompleter (getExtraHints) {
  const createSuggestions = function (model, textUntilPosition) {
    let text = model.getValue()
    textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, '').replace(/(\s+|\.)/g, ' ')
    let arr = textUntilPosition.split(/[\s;]/)
    let activeStr = arr[arr.length - 1]
    let len = activeStr.length
    let rexp = new RegExp('([^\\w]|^)' + activeStr + '\\w*', 'gim')
    let match = text.match(rexp)
    let mergeHints = Array.from(new Set([...getExtraHints(model)]))
      .sort()
      .filter(ele => {
        let rexp = new RegExp(ele.substr(0, len), 'gim')
        return (match && match.length === 1 && ele === activeStr) ||
                    ele.length === 1 ? false : activeStr.match(rexp)
      })
    return mergeHints.map(ele => ({
      label: ele,
      kind: monaco.languages.CompletionItemKind.Text,
      documentation: ele,
      insertText: ele
    }))
  }
  return {
    provideCompletionItems (model, position) {
      let textUntilPosition = model.getValueInRange({
        startLineNumber: position.lineNumber,
        startColumn: 1,
        endLineNumber: position.lineNumber,
        endColumn: position.column
      })
      return { suggestions: createSuggestions(model, textUntilPosition) }
    }
  }
}
export default createCompleter

Util/log-language.js

function registerLanguage (monaco) {
  monaco.languages.register({
    id: 'log'
  })
  monaco.languages.setMonarchTokensProvider('log', {
    tokenizer: {
      root: [
        [/(^[=a-zA-Z].*|\d\s.*)/, 'log-normal'],
        [/\sERROR\s.*/, 'log-error'],
        [/\sWARN\s.*/, 'log-warn'],
        [/\sINFO\s.*/, 'log-info'],
        [
          /^([0-9]{4}||[0-9]{2})-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
          'log-date'
        ],
        [
          /^[0-9]{2}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
          'log-date'
        ],
        [/(^\*\*Waiting queue:.*)/, 'log-info'],
        [/(^\*\*result tips:.*)/, 'log-info']
      ]
    }
  })
  monaco.editor.defineTheme('log', {
    base: 'vs',
    inherit: true,
    rules: [{
      token: 'log-info',
      foreground: '4b71ca'
    },
    {
      token: 'log-error',
      foreground: 'ff0000',
      fontStyle: 'bold'
    },
    {
      token: 'log-warn',
      foreground: 'FFA500'
    },
    {
      token: 'log-date',
      foreground: '008800'
    },
    {
      token: 'log-normal',
      foreground: '808080'
    }
    ],
    colors: {
      'editor.lineHighlightBackground': '#ffffff',
      'editorGutter.background': '#f7f7f7'
    }
  })
}

export default registerLanguage

Util/sql-completion.js

/* eslint-disable */
 import * as monaco from 'monaco-editor'
const hints = [
  'SELECT',
  'INSERT',
  'DELETE',
  'UPDATE',
  'CREATE TABLE',
  'DROP TABLE',
  'ALTER TABLE',
  'CREATE VIEW',
  'DROP VIEW',
  'CREATE INDEX',
  'DROP INDEX',
  'CREATE PROCEDURE',
  'DROP PROCEDURE',
  'CREATE TRIGGER',
  'DROP TRIGGER',
  'CREATE SCHEMA',
  'DROP SCHEMA',
  'CREATE DOMAIN',
  'ALTER DOMAIN',
  'DROP DOMAIN',
  'GRANT',
  'DENY',
  'REVOKE',
  'COMMIT',
  'ROLLBACK',
  'SET TRANSACTION',
  'DECLARE',
  'EXPLAN',
  'OPEN',
  'FETCH',
  'CLOSE',
  'PREPARE',
  'EXECUTE',
  'DESCRIBE',
  'FROM',
  'ORDER BY']
function createCompleter (getExtraHints) {
  const createSuggestions = function (model, textUntilPosition) {
    let text = model.getValue()
    textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, '').replace(/(\s+|\.)/g, ' ')
    let arr = textUntilPosition.split(/[\s;]/)
    let activeStr = arr[arr.length - 1]
    let len = activeStr.length
    let rexp = new RegExp('([^\\w]|^)' + activeStr + '\\w*', 'gim')
    let match = text.match(rexp)
    let textHints = !match ? []
      : match.map(ele => {
        let rexp = new RegExp(activeStr, 'gim')
        let search = ele.search(rexp)
        return ele.substr(search)
      })
    let mergeHints = Array.from(new Set([...hints, ...textHints, ...getExtraHints(model)]))
      .sort()
      .filter(ele => {
        let rexp = new RegExp(ele.substr(0, len), 'gim')
        return (match && match.length === 1 && ele === activeStr) ||
                    ele.length === 1 ? false : activeStr.match(rexp)
      })
    return mergeHints.map(ele => ({
      label: ele,
      kind: hints.indexOf(ele) > -1
        ? monaco.languages.CompletionItemKind.Keyword
        : monaco.languages.CompletionItemKind.Text,
      documentation: ele,
      insertText: ele
    }))
  }
  return {
    provideCompletionItems (model, position) {
      let textUntilPosition = model.getValueInRange({
        startLineNumber: position.lineNumber,
        startColumn: 1,
        endLineNumber: position.lineNumber,
        endColumn: position.column
      })
      return { suggestions: createSuggestions(model, textUntilPosition) }
    }
  }
}
export default createCompleter

在使用的頁面

 <monaco-editor
          v-model.trim="transformScript"
          language="javascript"
          style="height: 380px"
        />
  import MonacoEditor from '@/module/components/MonacoEditor/MonacoEditor'
 transformScript: '', // 執(zhí)行代碼文本
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容