Improve Code-Editor in Function-Nodes

This commit is contained in:
Jan Oberhauser 2019-09-04 18:22:06 +02:00
parent 48ccb36536
commit 2b2113433f
7 changed files with 107 additions and 6 deletions

View file

@ -62,6 +62,7 @@
"lodash.set": "^4.3.2",
"n8n-workflow": "^0.10.0",
"node-sass": "^4.12.0",
"prismjs": "^1.17.1",
"quill": "^2.0.0-dev.3",
"quill-autoformat": "^0.1.1",
"sass-loader": "^7.0.1",
@ -72,6 +73,7 @@
"vue": "^2.6.9",
"vue-cli-plugin-webpack-bundle-analyzer": "^1.3.0",
"vue-json-pretty": "^1.4.1",
"vue-prism-editor": "^0.3.0",
"vue-router": "^3.0.6",
"vue-template-compiler": "^2.5.17",
"vue-typed-mixins": "^0.1.0",

View file

@ -0,0 +1,60 @@
<template>
<div v-if="dialogVisible">
<el-dialog :visible="dialogVisible" append-to-body width="80%" :title="`Edit ${parameter.displayName}`" :before-close="closeDialog">
<div class="text-editor-wrapper ignore-key-press">
<div class="editor-description">
{{parameter.displayName}}:
</div>
<div class="text-editor" @keydown.stop>
<prism-editor :lineNumbers="true" :code="value" @change="valueChanged" language="js"></prism-editor>
</div>
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
// @ts-ignore
import PrismEditor from 'vue-prism-editor';
import {
Workflow,
} from 'n8n-workflow';
export default Vue.extend({
name: 'CodeEdit',
props: [
'dialogVisible',
'parameter',
'value',
],
components: {
PrismEditor,
},
data () {
return {
};
},
methods: {
valueChanged (value: string) {
this.$emit('valueChanged', value);
},
closeDialog () {
// Handle the close externally as the visible parameter is an external prop
// and is so not allowed to be changed here.
this.$emit('closeDialog');
return false;
},
},
});
</script>
<style scoped>
.editor-description {
font-weight: bold;
padding: 0 0 0.5em 0.2em;;
}
</style>

View file

@ -1,12 +1,19 @@
<template>
<div @keydown.stop :class="parameterInputClasses">
<expression-edit :dialogVisible="expressionEditDialogVisible" :value="value" :parameter="parameter" :path="path" @closeDialog="closeExpressionEditDialog" @valueChanged="expressionUpdated"></expression-edit>
<text-edit :dialogVisible="textEditDialogVisible" :value="value" :parameter="parameter" @closeDialog="closeTextEditDialog" @valueChanged="expressionUpdated"></text-edit>
<div class="parameter-input ignore-key-press" :style="parameterInputWrapperStyle">
<el-input v-if="['json', 'string'].includes(parameter.type)" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">
<font-awesome-icon v-if="!isValueExpression && !isReadOnly" slot="suffix" icon="external-link-alt" class="edit-window-button clickable" title="Open Edit Window" @click="textEditDialogVisible = true" />
</el-input>
<div v-if="['json', 'string'].includes(parameter.type)">
<code-edit :dialogVisible="codeEditDialogVisible" :value="value" :parameter="parameter" @closeDialog="closeCodeEditDialog" @valueChanged="expressionUpdated"></code-edit>
<text-edit :dialogVisible="textEditDialogVisible" :value="value" :parameter="parameter" @closeDialog="closeTextEditDialog" @valueChanged="expressionUpdated"></text-edit>
<div v-if="isEditor === true" class="clickable" @click="displayEditDialog()">
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
</div>
<el-input v-else ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">
<font-awesome-icon v-if="!isValueExpression && !isReadOnly" slot="suffix" icon="external-link-alt" class="edit-window-button clickable" title="Open Edit Window" @click="displayEditDialog()" />
</el-input>
</div>
<div v-else-if="parameter.type === 'dateTime'">
<el-date-picker
v-model="tempValue"
@ -109,7 +116,10 @@ import {
Workflow,
} from 'n8n-workflow';
import CodeEdit from '@/components/CodeEdit.vue';
import ExpressionEdit from '@/components/ExpressionEdit.vue';
// @ts-ignore
import PrismEditor from 'vue-prism-editor';
import TextEdit from '@/components/TextEdit.vue';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
@ -127,7 +137,9 @@ export default mixins(
.extend({
name: 'ParameterInput',
components: {
CodeEdit,
ExpressionEdit,
PrismEditor,
TextEdit,
},
props: [
@ -139,6 +151,7 @@ export default mixins(
],
data () {
return {
codeEditDialogVisible: false,
nodeName: '',
expressionAddOperation: 'set' as 'add' | 'set',
expressionEditDialogVisible: false,
@ -324,6 +337,9 @@ export default mixins(
isDefault (): boolean {
return this.parameter.default === this.value;
},
isEditor (): boolean {
return this.getArgument('editor') === 'code';
},
isValueExpression () {
if (this.parameter.noDataExpression === true) {
return false;
@ -401,12 +417,24 @@ export default mixins(
this.remoteParameterOptionsLoading = false;
},
closeCodeEditDialog () {
this.codeEditDialogVisible = false;
},
closeExpressionEditDialog () {
this.expressionEditDialogVisible = false;
},
closeTextEditDialog () {
this.textEditDialogVisible = false;
},
displayEditDialog () {
console.log('displayEditDialog...');
if (this.isEditor) {
this.codeEditDialogVisible = true;
} else {
this.textEditDialogVisible = true;
}
},
getArgument (argumentName: string): string | number | boolean | undefined {
if (this.parameter.typeOptions === undefined) {
return undefined;
@ -431,7 +459,7 @@ export default mixins(
}
if (this.parameter.type === 'string' && this.getArgument('alwaysOpenEditWindow')) {
this.textEditDialogVisible = true;
this.displayEditDialog();
return;
}

View file

@ -146,6 +146,10 @@ Vue.component('font-awesome-icon', FontAwesomeIcon);
Vue.config.productionTip = false;
import "prismjs";
import "prismjs/themes/prism.css";
import "vue-prism-editor/dist/VuePrismEditor.css";
new Vue({
router,
store,

View file

@ -27,11 +27,13 @@ export class Function implements INodeType {
name: 'functionCode',
typeOptions: {
alwaysOpenEditWindow: true,
editor: 'code',
rows: 10,
},
type: 'string',
default: 'items[0].json.myVariable = 1;\nreturn items;',
description: 'The JavaScript code to execute.',
noDataExpression: true,
},
],
};

View file

@ -29,11 +29,13 @@ export class FunctionItem implements INodeType {
name: 'functionCode',
typeOptions: {
alwaysOpenEditWindow: true,
editor: 'code',
rows: 10,
},
type: 'string',
default: 'item.myVariable = 1;\nreturn item;',
description: 'The JavaScript code to execute for each item.',
noDataExpression: true,
},
],
};

View file

@ -296,8 +296,11 @@ export interface INodeParameters {
export type NodePropertyTypes = 'boolean' | 'collection' | 'color' | 'dateTime' | 'fixedCollection' | 'json' | 'multiOptions' | 'number' | 'options' | 'string';
export type EditorTypes = 'code';
export interface INodePropertyTypeOptions {
alwaysOpenEditWindow?: boolean; // Supported by: string
editor?: EditorTypes; // Supported by: string
loadOptionsMethod?: string; // Supported by: options
maxValue?: number; // Supported by: number
minValue?: number; // Supported by: number
@ -307,7 +310,7 @@ export interface INodePropertyTypeOptions {
numberStepSize?: number; // Supported by: number
password?: boolean; // Supported by: string
rows?: number; // Supported by: string
[key: string]: boolean | number | string | undefined;
[key: string]: boolean | number | string | EditorTypes | undefined;
}
export interface IDisplayOptions {