vue3 可编辑的git代码差异显示插件(已废弃)

339 阅读2分钟

实现效果

Snipaste_2023-07-07_09-53-58.png

image.png

代码差异

参考vue-diff实现即可

可编辑

这个功能通过 div 的 contenteditable 功能实现,只要设置为 true 就相当于打开了编辑

监听输入事件

通过 @input 监听输入事件,然后触发更新的函数,重新渲染

<div contenteditable @input="handleInput">
</div>
    handleInput(e){
      const text = e.target.innerText
      this.$emit('handleInput', text, e.target)
    },

保留焦点

这个需要 selectionrange 来实现,因为我是重新绘制的页面元素,导致必定失去焦点,整体思路就是输入时记录位置,重绘时根据位置信息重新设置焦点 selectionrange 的相关介绍:

  1. 选区也有大学问!前端 Selection & Range 深度介绍
  2. Selection
  3. Range

记录位置,

  • index 是编辑的那个行的索引
  • el 是前面代码 emit 的 e.target,也是我们可以编辑的div元素
    saveFocus(index, el){
      // 获取焦点所在的区域
      const selection = window.getSelection()
      if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        let preRange = range.cloneRange()
        // range大概率是一部分的文本,所以需要设置节点内容为大的div元素,然后获取偏移量
        preRange.selectNodeContents(el)
        preRange.setEnd(range.endContainer, range.endOffset)
        this.globalStore.setInputPosition(index, preRange.toString().length)
      }
    }

重新设置焦点

    setFocus(){
      // 是当前这个索引的 Line 组件才会触发重新设置焦点
      if(this.meta.index === this.inputPosition?.index){
        let el = this.$refs.expect_editor?.[0]
        el.focus() // 重新使可编辑的div获取焦点,不然下面的getSelection得不到想要的结果
        let selection = window.getSelection()
        var range = document.createRange();

        let offset = this.inputPosition?.offset
        let currentNode = null
        let previousNode = null
        let childNodes = el.childNodes
        // 因为我里面套了两层别的元素,所以需要取出来
        if(childNodes.length === 1 && childNodes[0].localName === 'pre'){
          childNodes = childNodes[0].childNodes
        }
        if(childNodes.length === 1 && childNodes[0].localName === 'code'){
          childNodes = childNodes[0].childNodes
        }
        for(let i=0;i<childNodes.length;i++){
          previousNode = currentNode

          currentNode = childNodes[i]
          while (currentNode.childNodes.length>0) {
            currentNode = currentNode.childNodes[0]
          }

          if(previousNode !== null){
            offset -= previousNode.length
          }

          if(offset<=currentNode.length){
            break
          }
        }

        if(currentNode!==null){
          range.setStart(currentNode, offset)
          range.collapse(true)
          selection.removeAllRanges()
          selection.addRange(range)
        }
        this.globalStore.resetInputPosition() // 清空记录的位置信息
      }
    }

主要是 for 循环那段有点难理解,看明白 selectionrange 就能明白了,因为记录不了 range,只能计算处理偏移量,然后依次处理div里面的 range ,没达到偏移量就让记录的偏移量减少,直到找到那个 range 为止。

中文输入

这个时候出现了一个bug,中文输入是直接没有拼音的过程的,就是拼音不了,打不了中文。 然后我发现,中文的输入和英文的输入还不一样

image.png

所以只需要判断 inputTypeinsertCompositionText 就直接返回就行了

    handleInput(e){
      if(e?.inputType === 'insertCompositionText'){
        return
      }
      const text = e.target.innerText
      this.$emit('handleInput', text, e.target)
    },

但是这样最后一个输入的中文也会被跳过,从而不会触发更新的动作,所幸查了一下

compositionend

所以在 div 上再监听一个事件即可

<div contenteditable @input="handleInput" @compositionend="handleInput">
</div>

输入换行符

这个时候有个bug,就是输入换行符会直接出现两行新的行。

我看了一下输入就是两个 \n ,这就没办法了,根本解决不了,即使检测到两个 \n 手动改成一个 \n ,也不是长久之计

结尾

codeMirror 有代码差异显示的功能啊,那没事了

codemirror_merge 往下再翻翻,翻到merge那里

image.png

const editableText = `select 1 from dual;`
const leftText = `select 1 from dual`
const el = this.$refs.editor
CodeMirror.MergeView(el, {
        theme: 'idea',
        value: editableText,//比对中可以编辑的那一块
        origLeft: leftText,// 最左边,不可编辑,相当于prev
        lineNumbers: true,//显示行号
        mode: "sql",
        highlightDifferences: true,
        styleActiveLine: true,
        matchBrackets: true,
        connect: 'align',
        height: 200,
        lineWrapping: true, // 长行换行而不是滚动
      });

甚至还能三列进行对比,多设置一个 oriRight 就行了

对了,还需要多引入一个插件 diff-match-patch,不然会报错

//代码版本差异比较
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