100 lines
3.2 KiB
Vue
100 lines
3.2 KiB
Vue
<script lang="ts">
|
|
const External = Annotation.define<boolean>();
|
|
</script>
|
|
|
|
<script setup lang="ts">
|
|
import { dropCursor, crosshairCursor, keymap, EditorView, ViewUpdate, placeholder as placeholderExtension } from '@codemirror/view';
|
|
import { Annotation, EditorState } from '@codemirror/state';
|
|
import { indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language';
|
|
import { history, defaultKeymap, historyKeymap } from '@codemirror/commands';
|
|
import { search, searchKeymap } from '@codemirror/search';
|
|
import { closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
|
|
import { lintKeymap } from '@codemirror/lint';
|
|
|
|
const editor = useTemplateRef('editor');
|
|
const view = ref<EditorView>();
|
|
const state = ref<EditorState>();
|
|
|
|
const { placeholder } = defineProps<{
|
|
placeholder?: string
|
|
}>();
|
|
const model = defineModel<string>();
|
|
|
|
onMounted(() => {
|
|
if(editor.value)
|
|
{
|
|
state.value = EditorState.create({
|
|
doc: model.value,
|
|
extensions: [
|
|
history(),
|
|
search(),
|
|
dropCursor(),
|
|
EditorState.allowMultipleSelections.of(true),
|
|
indentOnInput(),
|
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
bracketMatching(),
|
|
closeBrackets(),
|
|
crosshairCursor(),
|
|
placeholderExtension(placeholder ?? ''),
|
|
EditorView.lineWrapping,
|
|
keymap.of([
|
|
...closeBracketsKeymap,
|
|
...defaultKeymap,
|
|
...searchKeymap,
|
|
...historyKeymap,
|
|
...foldKeymap,
|
|
...completionKeymap,
|
|
...lintKeymap
|
|
]),
|
|
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
|
if (viewUpdate.docChanged && !viewUpdate.transactions.some(tr => tr.annotation(External)))
|
|
{
|
|
model.value = viewUpdate.state.doc.toString();
|
|
}
|
|
}),
|
|
EditorView.contentAttributes.of({spellcheck: "true"}),
|
|
]
|
|
});
|
|
view.value = new EditorView({
|
|
state: state.value,
|
|
parent: editor.value,
|
|
});
|
|
}
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (view.value) {
|
|
view.value?.destroy()
|
|
view.value = undefined
|
|
}
|
|
});
|
|
|
|
watchEffect(() => {
|
|
if (model.value === void 0) {
|
|
return;
|
|
}
|
|
const currentValue = view.value ? view.value.state.doc.toString() : "";
|
|
if (view.value && model.value !== currentValue) {
|
|
view.value.dispatch({
|
|
changes: { from: 0, to: currentValue.length, insert: model.value || "" },
|
|
annotations: [External.of(true)],
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="editor" class="flex flex-1 w-full justify-stretch items-stretch border border-light-35 dark:border-dark-35 caret-light-100 dark:caret-dark-100" />
|
|
</template>
|
|
|
|
<style>
|
|
.cm-editor
|
|
{
|
|
@apply bg-transparent;
|
|
}
|
|
.cm-editor .cm-content
|
|
{
|
|
@apply caret-light-100;
|
|
@apply dark:caret-dark-100;
|
|
}
|
|
</style> |