web在线编辑器(vue版)

目录

  • 前言
  • 一、monaco-editor
    • 1、编辑器源码
    • 2、对比diff
    • 2、体积优化
  • 二、ace-editor?
    • 1、源码
    • 2、体积优化
  • 3、codemirror
    • 1、diff
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、monaco-editor

1、编辑器源码

web在线编辑器(vue版)_第1张图片

<template>
  <div ref="monacoEditor" id="monacoEditor" :style="style"></div>
</template>

<script setup>
import { nextTick, onMounted, ref, watch, onBeforeUnmount } from "vue";
import * as monaco from "monaco-editor";
const props = defineProps({
  modelValue: {},
  style: {},
  readOnly: {},
  language: {},
  content: {},
});

const emit = defineEmits(["update:modelValue"]);

const monacoEditor = ref();
let editor;

const init = () => {
  /**
   * @param wordWrap 自动换行,注意大小写
   */
  editor = monaco.editor.create(monacoEditor.value, {
    automaticLayout: true,
    value: props.modelValue,
    readOnly: props.readOnly,
    theme: "vs-dark",
    language: props.language,
    wordWrap: "on",
    wrappingIndent: "indent",
  });

  // 监听值的变化
  editor.onDidChangeModelContent(() => {
    emit("update:modelValue", editor.getValue());
  });
};
onMounted(() => {
  init();
});

watch(
  () => props.language,
  (nv, ov) => {
    monaco.editor.setModelLanguage(editor.getModel(), nv);
  }
);

function updateValue() {
  setTimeout(() => {
    editor.setValue(props.modelValue);
  }, 200);
}

watch(
  () => props.language,
  (newValue) => {
    monaco.editor.setModelLanguage(editor.getModel(), newValue);
  }
);
defineExpose({ updateValue });
</script>

<style></style>


2、对比diff

web在线编辑器(vue版)_第2张图片

ps:在比较过程中,支持在右侧的进行文件编辑。可通过界面顶部操作栏,查看当前修改个数(实时更改)。

当前修改处,可通过点击下一个,上一个跳转。当用户选择某行,当前修改处也会发生变化,

需要注意的是,如果当前选中行,不在修改的范围内,则显示0,点击跳转按钮,将跳转到离当前最近的一个修改,并且标识当前处于第几个修改

<template>
  <div class="monaco-diff-box">
    <div class="layout-start operate">
      <div style="width: 50%">{{ leftText }}</div>
      <div style="width: 10%">{{ rightText }}</div>
      <div class="layout-end" style="width: 40%">
        <span
          >发现&nbsp;<span class="config-diff-count">{{ diffCount }}</span
          >&nbsp;处修改</span
        >
        <span
          >,当前第&nbsp;<span class="config-diff-count">{{
            curDiffCount || 0
          }}</span
          >&nbsp;&nbsp;&nbsp;</span
        >
        <span
          title="上一个"
          v-debounce-click="
            () => {
              exeCommand('goPrevDiff');
            }
          "
          class="diffJump"
        >
          <el-icon><CaretLeft /></el-icon>
          上一个
        </span>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <span
          title="下一个"
          v-debounce-click="
            () => {
              exeCommand('goNextDiff');
            }
          "
          class="diffJump"
        >
          下一个
          <el-icon><CaretRight /></el-icon>
        </span>
      </div>
    </div>
    <div ref="monacoDiff" id="monacoDiff" :style="style"></div>
  </div>
</template>

<script setup>
import {
  nextTick,
  onMounted,
  ref,
  watch,
  onBeforeUnmount,
  reactive,
  computed,
} from "vue";
import * as monaco from "monaco-editor";
const props = defineProps({
  newCode: {},
  oldCode: {},
  params: {},
  language: {},
});

let leftText = ref(props.params.leftText || "修改前");
let rightText = ref(props.params.rightText || "修改后");

const monacoDiff = ref();
const lineChangesDataList = ref([]);
// 当前一共有多少处修改
let diffCount = computed(() => {
  return lineChangesDataList.value.length;
});
// 当前cursor指示的几处修改
let curDiffCount = ref(1);
var editor = null;
var decorations = null;
var navi;
var lineChangesDataMap = new Map();

const init = () => {
  editor = monaco.editor.createDiffEditor(monacoDiff.value, {
    automaticLayout: true,
    readOnly: false,
    wordWrap: "on",
    theme: "vs",
    wrappingIndent: "indent",
    bracketPairColorization: true,
    diffCodeLens: true,
    experimental: {
      collapseUnchangedRegions: true, // 折叠未更改区域
    },
    enableSplitViewResizing: false, // 允许用户调整差异编辑器拆分视图的大小。默认为 true。
  });

  let originalModel = monaco.editor.createModel(props.oldCode, props.language);
  let modifiedModel = monaco.editor.createModel(props.newCode, props.language);

  editor.setModel({
    original: originalModel,
    modified: modifiedModel,
  });

  // decorations = editor.createDecorationsCollection([
  //   {
  //     range: new monaco.Range(3, 1, 3, 1),
  //     options: {
  //       isWholeLine: true,
  //       className: "myContentClass",
  //       glyphMarginClassName: "myGlyphMarginClass",
  //     },
  //   },
  // ]);

  navi = monaco.editor.createDiffNavigator(editor, {
    followsCaret: true,
    ignoreCharChanges: true,
  });
};
onMounted(() => {
  init();
  setTimeout(() => {
    editor.onDidUpdateDiff((val) => {
      lineChangesDataList.value = editor.getLineChanges();

      lineChangesDataList.value.forEach((item, index) => {
        if (item.modifiedEndLineNumber == item.modifiedStartLineNumber) {
          lineChangesDataMap.set(item.modifiedStartLineNumber, index + 1);
        } else {
          for (
            let i = item.modifiedStartLineNumber;
            i <= item.modifiedEndLineNumber;
            i++
          ) {
            lineChangesDataMap.set(i, index + 1);
          }
        }
      });
      curDiffCount.value = lineChangesDataMap.get(
        editor.getPosition().lineNumber
      );
    });
    lineChangesDataList.value = editor.getLineChanges();
    lineChangesDataList.value.forEach((item, index) => {
      if (item.modifiedEndLineNumber == item.modifiedStartLineNumber) {
        lineChangesDataMap.set(item.modifiedStartLineNumber, index + 1);
      } else {
        for (
          let i = item.modifiedStartLineNumber;
          i <= item.modifiedEndLineNumber;
          i++
        ) {
          lineChangesDataMap.set(i, index + 1);
        }
      }
    });

    editor.getModifiedEditor().onMouseDown((e) => {
      lineChangesDataList.value = editor.getLineChanges();

      lineChangesDataList.value.forEach((item, index) => {
        if (item.modifiedEndLineNumber == item.modifiedStartLineNumber) {
          lineChangesDataMap.set(item.modifiedStartLineNumber, index + 1);
        } else {
          for (
            let i = item.modifiedStartLineNumber;
            i <= item.modifiedEndLineNumber;
            i++
          ) {
            lineChangesDataMap.set(i, index + 1);
          }
        }
      });
      curDiffCount.value = lineChangesDataMap.get(e.target.position.lineNumber);
    });

    editor.getOriginalEditor().onMouseDown((e) => {
      lineChangesDataList.value = editor.getLineChanges();

      lineChangesDataList.value.forEach((item, index) => {
        if (item.modifiedEndLineNumber == item.modifiedStartLineNumber) {
          lineChangesDataMap.set(item.modifiedStartLineNumber, index + 1);
        } else {
          for (
            let i = item.modifiedStartLineNumber;
            i <= item.modifiedEndLineNumber;
            i++
          ) {
            lineChangesDataMap.set(i, index + 1);
          }
        }
      });
      curDiffCount.value = lineChangesDataMap.get(e.target.position.lineNumber);
    });
  }, 200);
});

/**
 * @desc 跳转修改处
 * @cmd string
 * goNextDiff 前进异步
 * goPrevDiff 后退一步
 */
const exeCommand = (cmd) => {
  let arr = Array.from(lineChangesDataMap.keys());
  let positionIndex;
  let lineNumber = editor.getPosition().lineNumber;
  let hasNewNum = false;

  if (arr.indexOf(lineNumber) == -1) {
    hasNewNum = true;
    arr.push(lineNumber);
    arr = arr.sort((a, b) => {
      return a - b;
    });
  }

  positionIndex = arr.indexOf(lineNumber);

  if (cmd === "goNextDiff") {
    setTimeout(() => {
      //下一步
      if (
        curDiffCount.value == lineChangesDataList.value.length ||
        (hasNewNum && arr[arr.length - 1] == lineNumber)
      ) {
        curDiffCount.value = 1;
      } else {
        curDiffCount.value = lineChangesDataMap.get(arr[positionIndex + 1]);
      }
      navi.next();
    }, 300);
  } else if (cmd === "goPrevDiff") {
    setTimeout(() => {
      //上一步
      if (curDiffCount.value == 1 || (hasNewNum && arr[0] == lineNumber)) {
        curDiffCount.value = lineChangesDataList.value.length;
      } else {
        curDiffCount.value = lineChangesDataMap.get(arr[positionIndex - 1]);
      }
      nextTick(() => {
        navi.previous();
      });
    });
  }
};

/**
 * @desc 获取最新编辑数据
 */
const getValue = () => {
  return editor.getModel().modified.getValue();
}

watch(
  () => props.language,
  (nv, ov) => {
    // monaco.editor.setModelLanguage(editor.getModel(), nv);
  }
);

defineExpose({
  getValue,
});
</script>

<style lang="less">
.monaco-diff-box {
  height: 100%;
  .operate {
    height: 36px;
    .config-diff-count {
      color: #ff5722;
    }
    .diffJump {
      cursor: pointer;
    }
  }
  #monacoDiff {
    flex: 1;
    height: calc(100% - 36px);
    .monaco-editor .line-numbers.active-line-number {
      color: #ff5722;
    }
  }
}
</style>

2、体积优化

待续…

二、ace-editor?

1、源码

代码如下(示例):

<template>
  <v-ace-editor
    v-model:value="modelValue"
    @init="init"
    lang="json"
    :theme="theme"
    :options="options"
    :readonly="readonly"
    :style="style"
    />
</template>

<script setup>
import { VAceEditor } from "vue3-ace-editor";
import "ace-builds/webpack-resolver";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-chrome";
import "ace-builds/src-noconflict/ext-language_tools";

const props = defineProps({
    modelValue: {},
    theme: {},
    options: {},
    readonly: {},
    //自定义行内样式
    style: {},
})

</script>

<style>

</style>
<ace-editor
  v-model:value="tempFlow.textareashow"
  @init="initFail"
  lang="json"
  :theme="aceConfig.theme"
  :options="aceConfig.options"
  :readonly="aceConfig.readOnly"
  class="ace-editor"
/>

2、体积优化

待续…

3、codemirror

1、diff

web在线编辑器(vue版)_第3张图片

<template>
  <div class="config-diff">
    <div class="config-diff-title">
      <div class="layout-start">
        <div style="width: 50%">{{ leftText }}</div>
        <div style="width: 10%">{{ rightText }}</div>
        <div class="layout-end" style="width: 40%">
          <span
            >发现&nbsp;<span class="config-diff-count">{{ diffCount }}</span
            >&nbsp;处修改</span
          >
          <span
            >,当前第&nbsp;<span class="config-diff-count">{{
              curDiffCount
            }}</span
            >&nbsp;</span
          >
          <span
            title="上一个"
            v-debounce-click="
              () => {
                exeCommand('goPrevDiff');
              }
            "
            class="diffJump"
          >
            <el-icon><CaretLeft /></el-icon>
            上一个
          </span>
          &nbsp;&nbsp;&nbsp;&nbsp;
          <span
            title="下一个"
            v-debounce-click="
              () => {
                exeCommand('goNextDiff');
              }
            "
            class="diffJump"
          >
            下一个
            <el-icon><CaretRight /></el-icon>
          </span>
        </div>
      </div>
    </div>
    <div class="merge-view-main">
      <div class="file-editor" id="config-diff-view" ref="configDiffView"></div>
    </div>
  </div>
</template>

<script setup>
import { ref, getCurrentInstance, onMounted, watch } from "vue";
import _ from "lodash";
// 引入全局实例
import CodeMirror from "codemirror";
// 核心样式
import "codemirror/lib/codemirror.css";
// 引入主题后还需要在 options 中指定主题才会生效
import "codemirror/theme/idea.css";

// 需要引入具体的语法高亮库才会有对应的语法高亮效果
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
import "codemirror/mode/javascript/javascript.js";
import "codemirror/mode/css/css.js";
import "codemirror/mode/xml/xml.js";
import "codemirror/mode/shell/shell.js";
import "codemirror/mode/sql/sql.js";

//代码补全提示
import "codemirror/addon/hint/anyword-hint.js";
import "codemirror/addon/hint/css-hint.js";
import "codemirror/addon/hint/html-hint.js";
import "codemirror/addon/hint/javascript-hint.js";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/show-hint.js";
import "codemirror/addon/hint/sql-hint.js";
import "codemirror/addon/hint/xml-hint.js";

//代码版本差异比较
import "codemirror/addon/merge/merge.js";
import "codemirror/addon/merge/merge.css";
import DiffMatchPatch from "diff-match-patch";

window.diff_match_patch = DiffMatchPatch;
window.DIFF_DELETE = -1;
window.DIFF_INSERT = 1;
window.DIFF_EQUAL = 0;

const props = defineProps({
  newCode: {},
  oldCode: {},
  params: {},
});

const { proxy } = getCurrentInstance();

//文件后缀和codemirror模式
const FILE_POSTFIX = {
  xml: "application/xml",
  json: "application/json",
  txt: "text/html",
  lua: "text/x-lua",
  py: "text/x-python",
  js: "text/javascript",
  sh: "text/x-sh",

  other: "text/x-textile",
};

let configDiffView = ref(null);

var dv;
// 当前一共有多少处修改
let diffCount = ref(0);

// 当前cursor指示的几处修改
let curDiffCount = ref(0);

let newCode = "";
let oldCode = "";

let leftText = ref(props.params.leftText || "修改前");
let rightText = ref(props.params.rightText || "修改后");

const postfix = proxy.$commonSvc.getFilePostfix(props.params.name);
const mode = FILE_POSTFIX[postfix] || FILE_POSTFIX.other;

function initUI(target) {
  if (oldCode == null) return;

  target.innerHTML = "";
  dv = CodeMirror.MergeView(target, {
    value: newCode,
    origLeft: oldCode,
    orig: null,
    lineNumbers: true, //显示行号
    lineWrapping: true,
    readOnly: false,
    mode: mode,
    autofocus: true,
    foldGutter: true, //代码折叠
    highlightDifferences: "highlight", //有差异的地方是否高亮
    collapseIdentical: true,
    connect: null,
  });

  setTimeout(function () {
    dv.editor().refresh();
    dv.leftOriginal() && dv.leftOriginal().refresh();
  }, 500);

  var diffs = dv.leftChunks();
  diffCount.value = (diffs && diffs.length) || 0;
  curDiffCount.value = 0;
}

/**
 * @desc exeCommand 执行codemirror mergeview的命令类型
 * @param {*} cmd
 * 前进一步(goNextDiff) or
 * 后退异步(goPrevDiff)
 */
const exeCommand = (cmd) => {
  if (diffCount.value == 0) {
    return;
  } else {
    return new Promise((resolve, reject) => {
      if (dv) {
        if (cmd === "goNextDiff") {
          //下一步
          if (curDiffCount.value < diffCount.value) {
            ++curDiffCount.value;
          } else {
            curDiffCount.value = 1;
          }
        } else if (cmd === "goPrevDiff") {
          //上一步
          if (curDiffCount.value > 1) {
            curDiffCount.value--;
          } else {
            curDiffCount.value = diffCount.value;
          }
        }

        if (curDiffCount.value === 0) return;
        // 延时,等待diffView刷新后,在定位,存在一定的误差,主要在于不确定diffView刷新需要的时间
        setTimeout(function () {
          proxy.$commonSvc.highlightCodeMirror(
            configDiffView.value,
            curDiffCount.value
          );
        }, 300);
      }
    });
  }
};

/**
 * @desc getMergeConfig
 */
const getMergeConfig = () => {
  if (!dv) {
    return;
  }

  var edit = dv.edit;

  if (!edit) {
    return;
  }

  return edit.getValue();
};

onMounted(() => {
  newCode = props.newCode;
  oldCode = props.oldCode;
  var target = configDiffView.value;
  initUI(target);
});

watch(
  () => [props.newCode, props.params],
  (newValue) => {
    newCode = props.newCode;
    oldCode = props.oldCode;
    var target = configDiffView.value;
    initUI(target);
  },
  {
    deep: true,
  }
);

defineExpose({
  getMergeConfig,
});
</script>

<style lang="less" scoped>
.config-diff {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 0 16px;

  .config-diff-title {
    height: 36px;

    .config-diff-count {
      color: #ff5722;
    }
  }

  .merge-view-main {
    flex: 1;

    .file-editor {
      height: 600px;

      .CodeMirror-merge,
      .CodeMirror {
        z-index: 88888;
        height: 520px !important;
      }
    }

    .light-cursor {
      background: #b390f7;
    }
  }

  @media (min-width: 760px) {
    .merge-view-main {
      .file-editor {
        .CodeMirror-merge,
        .CodeMirror {
          height: 290px !important;
        }
      }
    }
  }

  @media (min-width: 1440px) {
    .merge-view-main {
      .file-editor {
        .CodeMirror-merge,
        .CodeMirror {
          height: 520px !important;
        }
      }
    }
  }

  .dev-test {
    .cm-s-blackboard.CodeMirror,
    .CodeMirror-merge-copybuttons-right,
    svg,
    .CodeMirror-merge-2pane .CodeMirror-merge-gap {
      height: 100%;
    }
  }

  .merge-event {
    .CodeMirror-merge-copy {
      pointer-events: auto !important;
    }
  }
}
.diffJump {
  cursor: pointer;
}
</style>


  /**
   * @desc codemirror merge next和pre 高亮
   * @param codeWrap {{string|Dom Element}} 需要检查的code所属的一个dom元素,目的缩小查询范围
   *  @param lineNumber {{number}} 需要高亮的行
   */
  highlightCodeMirror(codeWrap, curDiffCount) {
    if (!codeWrap) {
      return false;
    }

    var lines_left = $(".CodeMirror-merge-left", $(codeWrap))
      .find(".CodeMirror-linebackground")
      .get();
    var lines_right = $(".CodeMirror-merge-editor", $(codeWrap))
      .find(".CodeMirror-linebackground")
      .get();
    var merge_left = $(".CodeMirror-merge-left", $(codeWrap))
      .find(".CodeMirror-merge-l-chunk")
      .get();
    var merge_right = $(".CodeMirror-merge-editor", $(codeWrap))
      .find(".CodeMirror-merge-l-chunk")
      .get();
    var lineNumber_left;
    var lineNumber_right;
    var merge_left_end = $(".CodeMirror-merge-left", $(codeWrap)).find(
      ".CodeMirror-merge-l-chunk-end"
    );
    var merge_right_end = $(".CodeMirror-merge-editor", $(codeWrap)).find(
      ".CodeMirror-merge-l-chunk-end"
    );
    var lineNumber_left_end = merge_left_end
      .eq(curDiffCount - 1)
      .parent()
      .find(".CodeMirror-linenumber")
      .text();
    var lineNumber_right_end = merge_right_end
      .eq(curDiffCount - 1)
      .parent()
      .find(".CodeMirror-linenumber")
      .text();

    /**
     * @desc 定位每次修改高亮的起始和结束位置
     */
    for (var i = lines_left.length - 1; i >= 0; i--) {
      var temp = $(lines_left[i]).parent().find(".CodeMirror-linenumber");
      var value = parseInt($(temp).text());
      var value_end = parseInt(lineNumber_left_end);

      if (value <= value_end) {
        if (
          value < value_end &&
          $(lines_left[i]).attr("class").indexOf("-end") != -1
        ) {
          break;
        }

        if ($(lines_left[i]).attr("class").indexOf("-start") != -1) {
          lineNumber_left = $(temp).text();
        }
      }
    }

    for (var i = lines_right.length - 1; i >= 0; i--) {
      var temp = $(lines_right[i]).parent().find(".CodeMirror-linenumber");
      var value = parseInt($(temp).text());
      var value_end = parseInt(lineNumber_right_end);

      if (value <= value_end) {
        if (
          value < value_end &&
          $(lines_right[i]).attr("class").indexOf("-end") != -1
        ) {
          break;
        }

        if ($(lines_right[i]).attr("class").indexOf("-start") != -1) {
          lineNumber_right = $(temp).text();
        }
      }
    }

    /**
     * @desc 注入样式
     */
    merge_left.forEach(function (item) {
      var temp = $(item).parent().find(".CodeMirror-linenumber");

      if (
        lineNumber_left &&
        parseInt($(temp).text()) >= parseInt(lineNumber_left) &&
        parseInt($(temp).text()) <= parseInt(lineNumber_left_end)
      ) {
        $(item).addClass("light-cursor");
      } else {
        $(item).removeClass("light-cursor");
      }
    });

    merge_right.forEach(function (item) {
      var temp = $(item).parent().find(".CodeMirror-linenumber");

      if (
        lineNumber_right &&
        parseInt($(temp).text()) >= parseInt(lineNumber_right) &&
        parseInt($(temp).text()) <= parseInt(lineNumber_right_end)
      ) {
        $(item).addClass("light-cursor");
      } else {
        $(item).removeClass("light-cursor");
      }
    });
  },

总结

提示:这里对文章进行总结:

记录web在线编辑器的~

你可能感兴趣的:(前端,编辑器,vue.js)