feat: 增加技能管理模块试验性功能,等待优化
This commit is contained in:
164
easyflow-ui-admin/app/src/components/editor/CodeMirrorEditor.vue
Normal file
164
easyflow-ui-admin/app/src/components/editor/CodeMirrorEditor.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<script setup lang="ts">
|
||||
import {onBeforeUnmount, onMounted, ref, shallowRef, watch} from 'vue';
|
||||
|
||||
import {defaultKeymap, history, historyKeymap, indentWithTab} from '@codemirror/commands';
|
||||
import {javascript} from '@codemirror/lang-javascript';
|
||||
import {python} from '@codemirror/lang-python';
|
||||
import {
|
||||
bracketMatching,
|
||||
defaultHighlightStyle,
|
||||
indentOnInput,
|
||||
StreamLanguage,
|
||||
syntaxHighlighting
|
||||
} from '@codemirror/language';
|
||||
import {shell} from '@codemirror/legacy-modes/mode/shell';
|
||||
import {EditorState, type Extension} from '@codemirror/state';
|
||||
import {
|
||||
drawSelection,
|
||||
EditorView,
|
||||
highlightActiveLine,
|
||||
keymap,
|
||||
lineNumbers
|
||||
} from '@codemirror/view';
|
||||
|
||||
type EditorLanguage = 'javascript' | 'markdown' | 'python' | 'shell' | 'text';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: string;
|
||||
language?: EditorLanguage;
|
||||
readonly?: boolean;
|
||||
}>(),
|
||||
{
|
||||
language: 'text',
|
||||
readonly: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
|
||||
const hostRef = ref<HTMLElement>();
|
||||
const viewRef = shallowRef<EditorView>();
|
||||
let syncingFromOutside = false;
|
||||
|
||||
function languageExtension(): Extension[] {
|
||||
if (props.language === 'python') {
|
||||
return [python()];
|
||||
}
|
||||
if (props.language === 'javascript') {
|
||||
return [javascript({ jsx: true, typescript: true })];
|
||||
}
|
||||
if (props.language === 'shell') {
|
||||
return [StreamLanguage.define(shell)];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function createState(doc: string) {
|
||||
return EditorState.create({
|
||||
doc,
|
||||
extensions: [
|
||||
lineNumbers(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
highlightActiveLine(),
|
||||
indentOnInput(),
|
||||
bracketMatching(),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]),
|
||||
EditorView.editable.of(!props.readonly),
|
||||
EditorState.readOnly.of(props.readonly),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (!update.docChanged || syncingFromOutside) return;
|
||||
emit('update:modelValue', update.state.doc.toString());
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
height: '100%',
|
||||
fontSize: '13px',
|
||||
backgroundColor: 'var(--el-bg-color)',
|
||||
color: 'var(--el-text-color-primary)',
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
||||
},
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'var(--el-fill-color-lighter)',
|
||||
borderRight: '1px solid var(--el-border-color-lighter)',
|
||||
color: 'var(--el-text-color-secondary)',
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'var(--el-fill-color-light)',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'var(--el-fill-color-light)',
|
||||
},
|
||||
}),
|
||||
...languageExtension(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function mountEditor() {
|
||||
if (!hostRef.value) return;
|
||||
viewRef.value = new EditorView({
|
||||
state: createState(props.modelValue || ''),
|
||||
parent: hostRef.value,
|
||||
});
|
||||
}
|
||||
|
||||
function recreateEditor() {
|
||||
const currentDoc = viewRef.value?.state.doc.toString() ?? props.modelValue ?? '';
|
||||
viewRef.value?.destroy();
|
||||
viewRef.value = undefined;
|
||||
if (!hostRef.value) return;
|
||||
viewRef.value = new EditorView({
|
||||
state: createState(currentDoc),
|
||||
parent: hostRef.value,
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
const view = viewRef.value;
|
||||
if (!view) return;
|
||||
const nextValue = value || '';
|
||||
const currentValue = view.state.doc.toString();
|
||||
if (nextValue === currentValue) return;
|
||||
syncingFromOutside = true;
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: currentValue.length, insert: nextValue },
|
||||
});
|
||||
syncingFromOutside = false;
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.language, props.readonly],
|
||||
() => recreateEditor(),
|
||||
);
|
||||
|
||||
onMounted(mountEditor);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
viewRef.value?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="hostRef" class="code-mirror-editor"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.code-mirror-editor {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user