实现效果
代码差异
参考vue-diff实现即可
可编辑
这个功能通过 div 的 contenteditable 功能实现,只要设置为 true 就相当于打开了编辑
监听输入事件
通过 @input 监听输入事件,然后触发更新的函数,重新渲染
<div contenteditable @input="handleInput">
</div>
handleInput(e){
const text = e.target.innerText
this.$emit('handleInput', text, e.target)
},
保留焦点
这个需要 selection 和 range 来实现,因为我是重新绘制的页面元素,导致必定失去焦点,整体思路就是输入时记录位置,重绘时根据位置信息重新设置焦点
selection 和 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 循环那段有点难理解,看明白 selection 和 range 就能明白了,因为记录不了 range,只能计算处理偏移量,然后依次处理div里面的 range ,没达到偏移量就让记录的偏移量减少,直到找到那个 range 为止。
中文输入
这个时候出现了一个bug,中文输入是直接没有拼音的过程的,就是拼音不了,打不了中文。 然后我发现,中文的输入和英文的输入还不一样
所以只需要判断 inputType 为 insertCompositionText 就直接返回就行了
handleInput(e){
if(e?.inputType === 'insertCompositionText'){
return
}
const text = e.target.innerText
this.$emit('handleInput', text, e.target)
},
但是这样最后一个输入的中文也会被跳过,从而不会触发更新的动作,所幸查了一下
所以在 div 上再监听一个事件即可
<div contenteditable @input="handleInput" @compositionend="handleInput">
</div>
输入换行符
这个时候有个bug,就是输入换行符会直接出现两行新的行。
我看了一下输入就是两个 \n ,这就没办法了,根本解决不了,即使检测到两个 \n 手动改成一个 \n ,也不是长久之计
结尾
codeMirror 有代码差异显示的功能啊,那没事了
codemirror_merge 往下再翻翻,翻到merge那里
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