fix: fix expression editor bug causing code mirror to no longer be reactive

This commit is contained in:
Alex Grozav 2023-06-26 17:20:56 +03:00
parent 5784750940
commit 972eae880b
17 changed files with 62 additions and 65 deletions

View file

@ -9,7 +9,6 @@
}"
aria-checked="true"
>
<input type="radio" tabindex="-1" autocomplete="off" :class="$style.input" :value="value" />
<div
:class="{
[$style.button]: true,
@ -18,7 +17,6 @@
[$style.disabled]: disabled,
}"
:data-test-id="`radio-button-${value}`"
@click="$emit('click')"
>
{{ label }}
</div>

View file

@ -10,7 +10,7 @@
:active="modelValue === option.value"
:size="size"
:disabled="disabled || option.disabled"
@click="() => onClick(option)"
@click.prevent.stop="onClick(option)"
/>
</div>
</template>

View file

@ -1,21 +1,15 @@
<template>
<el-tooltip v-bind="{ ...$props, ...$attrs }">
<template v-for="(_, slotName) in $slots" #[slotName]>
<slot :name="slotName" />
<div
:key="slotName"
v-if="slotName === 'content' && buttons.length"
:class="$style.buttons"
:style="{ justifyContent: justifyButtons }"
>
<n8n-button
v-for="button in buttons"
:key="button.attrs.label"
v-bind="button.attrs"
v-on="button.listeners"
/>
</div>
<template #content>
<n8n-button
v-for="button in buttons"
:key="button.attrs.label"
v-bind="button.attrs"
v-on="button.listeners"
/>
<slot name="content"></slot>
</template>
<slot />
</el-tooltip>
</template>

View file

@ -69,6 +69,8 @@ export default defineComponent({
EditorView.updateListener.of((viewUpdate) => {
if (!this.editor || !viewUpdate.docChanged) return;
this.editorState = this.editor.state;
highlighter.removeColor(this.editor, this.plaintextSegments);
highlighter.addColor(this.editor, this.resolvableSegments);
@ -94,6 +96,7 @@ export default defineComponent({
}),
});
this.editorState = this.editor.state;
this.editor.focus();
highlighter.addColor(this.editor, this.resolvableSegments);

View file

@ -22,7 +22,7 @@
:path="path"
@focus="onFocus"
@blur="onBlur"
@update="onChange"
@change="onChange"
ref="inlineInput"
/>
<n8n-icon

View file

@ -106,6 +106,8 @@ export default defineComponent({
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged) return;
this.editorState = this.editor.state;
this.getHighlighter()?.removeColor(this.editor, this.htmlSegments);
this.getHighlighter()?.addColor(this.editor, this.resolvableSegments);
@ -270,6 +272,7 @@ export default defineComponent({
const state = EditorState.create({ doc, extensions: this.extensions });
this.editor = new EditorView({ parent: this.root(), state });
this.editorState = this.editor.state;
this.getHighlighter()?.addColor(this.editor, this.resolvableSegments);
},

View file

@ -114,6 +114,9 @@ export default defineComponent({
EditorView.updateListener.of((viewUpdate) => {
if (!this.editor || !viewUpdate.docChanged) return;
// Force segments value update by keeping track of editor state
this.editorState = this.editor.state;
highlighter.removeColor(this.editor, this.plaintextSegments);
highlighter.addColor(this.editor, this.resolvableSegments);
@ -137,10 +140,11 @@ export default defineComponent({
extensions,
}),
});
this.editorState = this.editor.state;
highlighter.addColor(this.editor, this.resolvableSegments);
this.$emit('update', {
this.$emit('change', {
value: this.unresolvedExpression,
segments: this.displayableSegments,
});

View file

@ -133,14 +133,14 @@
<n8n-input
v-else
v-model="tempValue"
:modelValue="tempValue"
ref="inputField"
class="input-with-opener"
:size="inputSize"
:type="getStringInputType"
:rows="getArgument('rows')"
:disabled="isReadOnly"
@update:modelValue="onTextInputChange($event) && valueChanged($event)"
@update:modelValue="onUpdateTextInput"
@keydown.stop
@focus="setFocus"
@blur="onBlur"
@ -220,7 +220,7 @@
:min="getArgument('minValue')"
:precision="getArgument('numberPrecision')"
:disabled="isReadOnly"
@update:modelValue="onTextInputChange($event) && valueChanged($event)"
@update:modelValue="onUpdateTextInput"
@focus="setFocus"
@blur="onBlur"
@keydown.stop
@ -1022,6 +1022,10 @@ export default defineComponent({
valueChangedDebounced(value: NodeParameterValueType | {} | Date) {
void this.callDebounced('valueChanged', { debounceTime: 100 }, value);
},
onUpdateTextInput(value: string) {
this.valueChanged(value);
this.onTextInputChange(value);
},
valueChanged(value: NodeParameterValueType | {} | Date) {
if (this.parameter.name === 'nodeCredentialType') {
this.activeCredentialType = value as string;

View file

@ -11,7 +11,7 @@
<template #options>
<parameter-options
:parameter="parameter"
:modelValue="value"
:value="value"
:isReadOnly="false"
:showOptions="true"
:isValueExpression="isValueExpression"

View file

@ -12,7 +12,7 @@
<parameter-options
v-if="displayOptions"
:parameter="parameter"
:modelValue="value"
:value="value"
:isReadOnly="isReadOnly"
:showOptions="displayOptions"
:showExpressionSelector="showExpressionSelector"
@ -234,12 +234,14 @@ export default defineComponent({
this.menuExpanded = expanded;
},
optionSelected(command: string) {
console.log('optionSelected', command);
this.eventBus.emit('optionSelected', command);
},
valueChanged(parameterData: IUpdateInformation) {
this.$emit('update', parameterData);
},
onTextInput(parameterData: IUpdateInformation) {
console.log('onTextInput', parameterData);
if (isValueExpression(this.parameter, parameterData.value)) {
this.eventBus.emit('optionSelected', 'addExpression');
}

View file

@ -18,7 +18,7 @@
:expressionEvaluated="expressionValueComputed"
:label="label"
:data-test-id="`parameter-input-${parameter.name}`"
:event-bus="internalEventBus"
:event-bus="eventBus"
@focus="onFocus"
@blur="onBlur"
@drop="onDrop"
@ -72,17 +72,6 @@ export default defineComponent({
ParameterInput,
InputHint,
},
data() {
return {
internalEventBus: createEventBus(),
};
},
mounted() {
this.eventBus.on('optionSelected', this.optionSelected);
},
beforeUnmount() {
this.eventBus.off('optionSelected', this.optionSelected);
},
props: {
isReadOnly: {
type: Boolean,
@ -231,9 +220,6 @@ export default defineComponent({
onDrop(data: string) {
this.$emit('drop', data);
},
optionSelected(command: string) {
this.internalEventBus.emit('optionSelected', command);
},
onValueChanged(parameterData: IUpdateInformation) {
this.$emit('update', parameterData);
},

View file

@ -55,7 +55,7 @@ export default defineComponent({
isReadOnly: {
type: Boolean,
},
modelValue: {
value: {
type: [Object, String, Number, Boolean, Array] as PropType<NodeParameterValueType>,
},
showOptions: {
@ -88,10 +88,10 @@ export default defineComponent({
},
computed: {
isDefault(): boolean {
return this.parameter.default === this.modelValue;
return this.parameter.default === this.value;
},
isValueExpression(): boolean {
return isValueExpression(this.parameter, this.modelValue);
return isValueExpression(this.parameter, this.value);
},
isHtmlEditor(): boolean {
return this.getArgument('editor') === 'htmlEditor';
@ -153,8 +153,8 @@ export default defineComponent({
if (
this.hasRemoteMethod ||
(this.parameter.type === 'resourceLocator' &&
isResourceLocatorValue(this.modelValue) &&
this.modelValue.mode === 'list')
isResourceLocatorValue(this.value) &&
this.value.mode === 'list')
) {
return [
{
@ -173,6 +173,7 @@ export default defineComponent({
this.$emit('menu-expanded', visible);
},
onViewSelected(selected: string) {
console.log('onViewSelected', selected);
if (selected === 'expression') {
this.$emit(
'update:modelValue',

View file

@ -146,11 +146,10 @@
></n8n-option>
</n8n-select>
<n8n-tooltip
placement="right"
v-if="canLinkRuns"
:content="$locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint')"
>
<n8n-tooltip placement="right" v-if="canLinkRuns">
<template #content>
{{ $locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
</template>
<n8n-icon-button
v-if="linkedRuns"
icon="unlink"

View file

@ -24,6 +24,7 @@ import {
highlightActiveLineGutter,
keymap,
lineNumbers,
ViewUpdate,
} from '@codemirror/view';
import {
MSSQL,
@ -167,6 +168,8 @@ export default defineComponent({
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged || !this.editor) return;
this.editorState = this.editor.state;
highlighter.removeColor(this.editor as EditorView, this.plaintextSegments);
highlighter.addColor(this.editor as EditorView, this.resolvableSegments);
@ -182,6 +185,7 @@ export default defineComponent({
const state = EditorState.create({ doc: this.modelValue, extensions: this.extensions });
this.editor = new EditorView({ parent: this.$refs.sqlEditor as HTMLDivElement, state });
this.editorState = this.editor.state;
highlighter.addColor(this.editor as EditorView, this.resolvableSegments);
},
methods: {

View file

@ -24,6 +24,7 @@ export const expressionManager = defineComponent({
return {
editor: {} as EditorView,
skipSegments: [] as string[],
editorState: undefined,
};
},
watch: {
@ -72,24 +73,24 @@ export const expressionManager = defineComponent({
},
segments(): Segment[] {
if (!this.editor?.state) return [];
if (!this.editorState || !this.editorState) return [];
const rawSegments: RawSegment[] = [];
const fullTree = ensureSyntaxTree(
this.editor.state,
this.editor.state.doc.length,
this.editorState,
this.editorState.doc.length,
EXPRESSION_EDITOR_PARSER_TIMEOUT,
);
if (fullTree === null) {
throw new Error(`Failed to parse expression: ${this.editor.state.doc.toString()}`);
throw new Error(`Failed to parse expression: ${this.editorValue}`);
}
const skipSegments = ['Program', 'Script', 'Document', ...this.skipSegments];
fullTree.cursor().iterate((node) => {
const text = this.editor.state.sliceDoc(node.from, node.to);
const text = this.editorState.sliceDoc(node.from, node.to);
if (skipSegments.includes(node.type.name)) return;

View file

@ -630,14 +630,6 @@ export default defineComponent({
NODE_CREATOR_OPEN_SOURCES,
};
},
beforeUnmount() {
this.resetWorkspace();
// Make sure the event listeners get removed again else we
// could add up with them registered multiple times
document.removeEventListener('keydown', this.keyDown);
document.removeEventListener('keyup', this.keyUp);
this.unregisterCustomAction('showNodeCreator');
},
methods: {
showTriggerMissingToltip(isVisible: boolean) {
this.showTriggerMissingTooltip = isVisible;
@ -3961,6 +3953,12 @@ export default defineComponent({
nodeViewEventBus.off('saveWorkflow', this.saveCurrentWorkflowExternal);
},
beforeUnmount() {
// Make sure the event listeners get removed again else we
// could add up with them registered multiple times
document.removeEventListener('keydown', this.keyDown);
document.removeEventListener('keyup', this.keyUp);
this.unregisterCustomAction('showNodeCreator');
this.resetWorkspace();
this.instance.unbind();
this.instance.destroy();

View file

@ -89,8 +89,8 @@ export default mergeConfig(
defineConfig({
define: {
// This causes test to fail but is required for actually running it
...(NODE_ENV !== 'test' ? { global: 'globalThis' } : {}),
...(NODE_ENV === 'development' ? { process: { env: {} } } : {}),
// ...(NODE_ENV !== 'test' ? { 'global': 'globalThis' } : {}),
...(NODE_ENV === 'development' ? { 'process.env': {} } : {}),
BASE_PATH: `'${publicPath}'`,
},
plugins,