132 lines
2.9 KiB
Vue
132 lines
2.9 KiB
Vue
<script lang="ts" setup>
|
|
import { javascript } from '@codemirror/lang-javascript';
|
|
import { yaml } from '@codemirror/lang-yaml';
|
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
import { MergeView } from '@codemirror/merge';
|
|
import { EditorView, basicSetup } from 'codemirror';
|
|
import { EditorState } from '@codemirror/state';
|
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
|
const emit = defineEmits(['update:newArea', 'change']);
|
|
const props = defineProps({
|
|
oldArea: {
|
|
type: String,
|
|
default: '原始内容',
|
|
},
|
|
/**当前变更内容 */
|
|
newArea: {
|
|
type: String,
|
|
default: '当前内容',
|
|
},
|
|
/**编辑框高度 */
|
|
height: {
|
|
type: String,
|
|
default: '400px',
|
|
},
|
|
/**缩进2空格 */
|
|
tabSize: {
|
|
type: Number,
|
|
default: 2,
|
|
},
|
|
/**是否禁止输入 */
|
|
disabled: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
/**高亮语言 */
|
|
lang: {
|
|
type: String,
|
|
default: 'javascript',
|
|
},
|
|
});
|
|
|
|
/**视图容器 */
|
|
const viewContainerDom = ref<HTMLElement | undefined>(undefined);
|
|
let viewContainer: MergeView | null = null;
|
|
|
|
/**高亮语言拓展 */
|
|
function fnLangExtension() {
|
|
if (props.lang === 'yaml') {
|
|
return yaml();
|
|
}
|
|
return javascript();
|
|
}
|
|
|
|
/**初始化渲染视图 */
|
|
function handleRanderView(container: HTMLElement | undefined) {
|
|
if (!container) return;
|
|
viewContainer = new MergeView({
|
|
a: {
|
|
doc: props.oldArea,
|
|
extensions: [
|
|
fnLangExtension(),
|
|
oneDark,
|
|
basicSetup,
|
|
EditorView.editable.of(false),
|
|
EditorState.readOnly.of(true),
|
|
],
|
|
},
|
|
b: {
|
|
doc: props.newArea,
|
|
extensions: [
|
|
fnLangExtension(),
|
|
oneDark,
|
|
basicSetup,
|
|
EditorView.editable.of(!props.disabled),
|
|
EditorState.readOnly.of(props.disabled),
|
|
EditorState.tabSize.of(props.tabSize),
|
|
EditorView.updateListener.of(v => {
|
|
if (v.docChanged) {
|
|
const docStr = v.state.doc.toString();
|
|
emit('change', docStr, v.state.doc);
|
|
// 禁用时不双向绑定,防止监听重复变化数值
|
|
if (!props.disabled) {
|
|
emit('update:newArea', docStr);
|
|
}
|
|
}
|
|
}),
|
|
],
|
|
},
|
|
parent: container,
|
|
});
|
|
}
|
|
|
|
/**监听是否value改变 */
|
|
watch(
|
|
() => props.newArea,
|
|
val => {
|
|
// 禁用时无输入靠外部值变化改变数值
|
|
if (props.disabled && viewContainer) {
|
|
const docLine = viewContainer.b.state.doc.length;
|
|
viewContainer.b.dispatch({
|
|
changes: { from: 0, to: docLine, insert: val },
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
onMounted(() => {
|
|
handleRanderView(viewContainerDom.value);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
viewContainer?.destroy();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="viewContainerDom"
|
|
class="container"
|
|
:style="{ '--editor-height': height }"
|
|
></div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.container {
|
|
--editor-height: 400px;
|
|
}
|
|
.container :deep(.cm-editor) {
|
|
height: var(--editor-height);
|
|
}
|
|
</style>
|