#add jira & message sync
This commit is contained in:
289
resources/js/components/env/CodeEditor.vue
vendored
Normal file
289
resources/js/components/env/CodeEditor.vue
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div class="codemirror-editor">
|
||||
<div ref="editorContainer" class="editor-container"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { EditorView, keymap, highlightActiveLine, highlightActiveLineGutter, lineNumbers, scrollPastEnd } from '@codemirror/view'
|
||||
import { EditorState, Compartment } from '@codemirror/state'
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
|
||||
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'
|
||||
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'
|
||||
import { foldGutter, indentOnInput, indentUnit, bracketMatching } from '@codemirror/language'
|
||||
import { highlightSelectionMatches as highlightSelection } from '@codemirror/search'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { php } from '@codemirror/lang-php'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import { env } from '../../lang-env.js'
|
||||
|
||||
export default {
|
||||
name: 'CodeEditor',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: 'env' // 'env', 'javascript', 'php'
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'light' // 'light', 'dark'
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
languageCompartment: new Compartment(),
|
||||
themeCompartment: new Compartment()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initEditor();
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue) {
|
||||
if (this.editor && newValue !== this.editor.state.doc.toString()) {
|
||||
this.editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this.editor.state.doc.length,
|
||||
insert: newValue || ''
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
language() {
|
||||
this.updateLanguage();
|
||||
},
|
||||
theme() {
|
||||
this.updateTheme();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.editor) {
|
||||
this.editor.destroy();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLanguageExtension() {
|
||||
switch (this.language) {
|
||||
case 'javascript':
|
||||
return javascript();
|
||||
case 'php':
|
||||
return php();
|
||||
case 'env':
|
||||
return env();
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
getThemeExtension() {
|
||||
return this.theme === 'dark' ? oneDark : [];
|
||||
},
|
||||
|
||||
initEditor() {
|
||||
const extensions = [
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightActiveLine(),
|
||||
foldGutter(),
|
||||
indentOnInput(),
|
||||
indentUnit.of(' '),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
autocompletion(),
|
||||
highlightSelectionMatches(),
|
||||
history(),
|
||||
scrollPastEnd(),
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
...historyKeymap,
|
||||
...completionKeymap,
|
||||
]),
|
||||
this.languageCompartment.of(this.getLanguageExtension()),
|
||||
this.themeCompartment.of(this.getThemeExtension()),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
const content = update.state.doc.toString();
|
||||
this.$emit('update:modelValue', content);
|
||||
this.$emit('change', content);
|
||||
}
|
||||
}),
|
||||
// 确保编辑器可以滚动
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
height: '100%'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: 'inherit',
|
||||
overflow: 'auto'
|
||||
},
|
||||
'.cm-content': {
|
||||
padding: '12px',
|
||||
minHeight: '100%'
|
||||
},
|
||||
'.cm-editor': {
|
||||
height: '100%'
|
||||
},
|
||||
'.cm-focused': {
|
||||
outline: 'none'
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
// 添加只读模式
|
||||
if (this.readonly) {
|
||||
extensions.push(EditorView.editable.of(false));
|
||||
}
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: this.modelValue || '',
|
||||
extensions
|
||||
});
|
||||
|
||||
this.editor = new EditorView({
|
||||
state,
|
||||
parent: this.$refs.editorContainer
|
||||
});
|
||||
},
|
||||
|
||||
updateLanguage() {
|
||||
if (!this.editor) return;
|
||||
|
||||
this.editor.dispatch({
|
||||
effects: this.languageCompartment.reconfigure(this.getLanguageExtension())
|
||||
});
|
||||
},
|
||||
|
||||
updateTheme() {
|
||||
if (!this.editor) return;
|
||||
|
||||
this.editor.dispatch({
|
||||
effects: this.themeCompartment.reconfigure(this.getThemeExtension())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.codemirror-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* CodeMirror 自定义样式 */
|
||||
.codemirror-editor :deep(.cm-editor) {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Consolas', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-scroller) {
|
||||
overflow: auto !important;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* 基本语法高亮样式 */
|
||||
.codemirror-editor :deep(.cm-variableName) {
|
||||
color: #0969da;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-string) {
|
||||
color: #0a3069;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-number) {
|
||||
color: #0550ae;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-keyword) {
|
||||
color: #8250df;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-operator) {
|
||||
color: #cf222e;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-comment) {
|
||||
color: #6a737d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-content) {
|
||||
padding: 12px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-focused) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
/* 深色主题样式 */
|
||||
.codemirror-editor.dark :deep(.cm-editor) {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.codemirror-editor.dark :deep(.cm-scroller)::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
.codemirror-editor.dark :deep(.cm-scroller)::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.codemirror-editor.dark :deep(.cm-scroller)::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user