282 lines
7.3 KiB
Vue
282 lines
7.3 KiB
Vue
|
<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>
|