hesabixCore/webUI/src/components/MonacoEditor.vue

282 lines
7.3 KiB
Vue
Raw Normal View History

<template>
<div ref="editorContainer" class="monaco-editor-container" :style="{ height: height }"></div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as monaco from 'monaco-editor'
// تنظیمات MonacoEnvironment برای web workers
if (typeof self !== 'undefined' && !self.MonacoEnvironment) {
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
try {
// استفاده از web workers موجود در پوشه public
if (label === 'json') {
return '/monaco-editor/min/vs/language/json/json.worker.js'
}
if (label === 'css' || label === 'scss' || label === 'less') {
return '/monaco-editor/min/vs/language/css/css.worker.js'
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return '/monaco-editor/min/vs/language/html/html.worker.js'
}
if (label === 'typescript' || label === 'javascript') {
return '/monaco-editor/min/vs/language/typescript/ts.worker.js'
}
return '/monaco-editor/min/vs/editor/editor.worker.js'
} catch (error) {
console.warn('Monaco Editor worker not found, falling back to main thread:', error)
// Fallback to main thread if workers fail
return null
}
}
}
}
export default {
name: 'MonacoEditor',
props: {
modelValue: {
type: String,
default: ''
},
language: {
type: String,
default: 'html'
},
theme: {
type: String,
default: 'vs-dark'
},
height: {
type: String,
default: '400px'
},
readOnly: {
type: Boolean,
default: false
},
options: {
type: Object,
default: () => ({})
}
},
emits: ['update:modelValue', 'change'],
setup(props, { emit }) {
const editorContainer = ref(null)
let editor = null
const createEditor = () => {
if (!editorContainer.value) return
try {
// تنظیمات پیش‌فرض
const defaultOptions = {
value: props.modelValue,
language: props.language,
theme: props.theme,
readOnly: props.readOnly,
automaticLayout: true,
minimap: {
enabled: false
},
scrollBeyondLastLine: false,
fontSize: 14,
fontFamily: 'Consolas, "Courier New", monospace',
lineNumbers: 'on',
roundedSelection: false,
scrollbar: {
vertical: 'visible',
horizontal: 'visible'
},
folding: true,
wordWrap: 'on',
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: 'on',
tabCompletion: 'on',
wordBasedSuggestions: 'on',
parameterHints: {
enabled: true
},
autoIndent: 'full',
formatOnPaste: true,
formatOnType: false,
quickSuggestions: true,
hover: {
enabled: true
}
}
// ترکیب تنظیمات پیش‌فرض با تنظیمات سفارشی
const editorOptions = { ...defaultOptions, ...props.options }
editor = monaco.editor.create(editorContainer.value, editorOptions)
// اضافه کردن event listener برای تغییرات
editor.onDidChangeModelContent(() => {
const value = editor.getValue()
emit('update:modelValue', value)
emit('change', value)
})
// تنظیم زبان فارسی برای RTL
if (props.language === 'html') {
monaco.editor.setModelLanguage(editor.getModel(), 'html')
}
} catch (error) {
console.error('Error creating Monaco Editor:', error)
// Fallback to a simple textarea if Monaco fails
const textarea = document.createElement('textarea')
textarea.value = props.modelValue
textarea.style.width = '100%'
textarea.style.height = '100%'
textarea.style.fontFamily = 'Consolas, "Courier New", monospace'
textarea.style.fontSize = '14px'
textarea.style.border = 'none'
textarea.style.outline = 'none'
textarea.style.resize = 'none'
textarea.addEventListener('input', (e) => {
emit('update:modelValue', e.target.value)
emit('change', e.target.value)
})
editorContainer.value.appendChild(textarea)
}
}
const destroyEditor = () => {
if (editor) {
try {
editor.dispose()
} catch (error) {
console.warn('Error disposing Monaco Editor:', error)
}
editor = null
}
// Clean up fallback textarea if it exists
if (editorContainer.value) {
const textarea = editorContainer.value.querySelector('textarea')
if (textarea) {
textarea.remove()
}
}
}
// تماشای تغییرات props
watch(() => props.modelValue, (newValue) => {
if (editor && newValue !== editor.getValue()) {
editor.setValue(newValue)
}
})
watch(() => props.language, (newLanguage) => {
if (editor) {
monaco.editor.setModelLanguage(editor.getModel(), newLanguage)
}
})
watch(() => props.theme, (newTheme) => {
if (editor) {
monaco.editor.setTheme(newTheme)
}
})
watch(() => props.readOnly, (newReadOnly) => {
if (editor) {
editor.updateOptions({ readOnly: newReadOnly })
}
})
onMounted(() => {
createEditor()
})
onBeforeUnmount(() => {
destroyEditor()
})
// متدهای عمومی برای دسترسی از خارج
const getValue = () => {
if (editor) {
return editor.getValue()
}
// Fallback for textarea
if (editorContainer.value) {
const textarea = editorContainer.value.querySelector('textarea')
return textarea ? textarea.value : ''
}
return ''
}
const setValue = (value) => {
if (editor) {
editor.setValue(value)
} else if (editorContainer.value) {
// Fallback for textarea
const textarea = editorContainer.value.querySelector('textarea')
if (textarea) {
textarea.value = value
}
}
}
const focus = () => {
if (editor) {
editor.focus()
}
}
const getEditor = () => {
return editor
}
return {
editorContainer,
getValue,
setValue,
focus,
getEditor
}
}
}
</script>
<style scoped>
.monaco-editor-container {
width: 100%;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
/* تنظیمات برای RTL */
.monaco-editor-container :deep(.monaco-editor) {
direction: ltr;
}
.monaco-editor-container :deep(.monaco-editor .margin) {
direction: ltr;
}
.monaco-editor-container :deep(.monaco-editor .monaco-editor-background) {
direction: ltr;
}
/* تنظیمات برای تم تاریک */
.monaco-editor-container :deep(.vs-dark) {
background-color: #1e1e1e;
}
/* تنظیمات برای تم روشن */
.monaco-editor-container :deep(.vs) {
background-color: #ffffff;
}
/* تنظیمات برای تم HC */
.monaco-editor-container :deep(.hc-black) {
background-color: #000000;
}
</style>