Files
n8n-enterprise-unlocked/packages/editor-ui/src/components/JsEditor/JsEditor.vue
Michael Kret 0de9d56619 feat(AI Transform Node): New node (#9990)
Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
Co-authored-by: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com>
2024-08-07 18:07:48 +03:00

136 lines
3.2 KiB
Vue

<template>
<div :class="$style.editor" :style="isReadOnly ? 'opacity: 0.7' : ''">
<div ref="jsEditorRef" class="ph-no-capture js-editor"></div>
<slot name="suffix" />
</div>
</template>
<script setup lang="ts">
import { history, toggleComment } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { foldGutter, indentOnInput } from '@codemirror/language';
import { lintGutter } from '@codemirror/lint';
import type { Extension } from '@codemirror/state';
import { EditorState, Prec } from '@codemirror/state';
import type { ViewUpdate } from '@codemirror/view';
import {
EditorView,
dropCursor,
highlightActiveLine,
highlightActiveLineGutter,
keymap,
lineNumbers,
} from '@codemirror/view';
import { computed, onMounted, ref, watch } from 'vue';
import {
autocompleteKeyMap,
enterKeyMap,
historyKeyMap,
tabKeyMap,
} from '@/plugins/codemirror/keymap';
import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
import { codeNodeEditorTheme } from '../CodeNodeEditor/theme';
type Props = {
modelValue: string;
isReadOnly?: boolean;
fillParent?: boolean;
rows?: number;
};
const props = withDefaults(defineProps<Props>(), { fillParent: false, isReadOnly: false, rows: 4 });
const emit = defineEmits<{
'update:modelValue': [value: string];
}>();
onMounted(() => {
createEditor();
});
watch(
() => props.modelValue,
(newValue: string) => {
const editorValue = editor.value?.state?.doc.toString();
// If model value changes from outside the component
if (
editorValue !== undefined &&
editorValue.length !== newValue.length &&
editorValue !== newValue
) {
destroyEditor();
createEditor();
}
},
);
function createEditor() {
const state = EditorState.create({ doc: props.modelValue, extensions: extensions.value });
const parent = jsEditorRef.value;
editor.value = new EditorView({ parent, state });
editorState.value = editor.value.state;
}
function destroyEditor() {
editor.value?.destroy();
}
const jsEditorRef = ref<HTMLDivElement>();
const editor = ref<EditorView | null>(null);
const editorState = ref<EditorState | null>(null);
const extensions = computed(() => {
const extensionsToApply: Extension[] = [
javascript(),
lineNumbers(),
EditorView.lineWrapping,
EditorState.readOnly.of(props.isReadOnly),
codeNodeEditorTheme({
isReadOnly: props.isReadOnly,
maxHeight: props.fillParent ? '100%' : '40vh',
minHeight: '20vh',
rows: props.rows,
}),
];
if (!props.isReadOnly) {
extensionsToApply.push(
history(),
Prec.highest(
keymap.of([
...tabKeyMap(),
...enterKeyMap,
...historyKeyMap,
...autocompleteKeyMap,
{ key: 'Mod-/', run: toggleComment },
]),
),
lintGutter(),
n8nAutocompletion(),
indentOnInput(),
highlightActiveLine(),
highlightActiveLineGutter(),
foldGutter(),
dropCursor(),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged || !editor.value) return;
emit('update:modelValue', editor.value?.state.doc.toString());
}),
);
}
return extensionsToApply;
});
</script>
<style lang="scss" module>
.editor {
height: 100%;
& > div {
height: 100%;
}
}
</style>