2019-06-23 03:35:23 -07:00
|
|
|
<template>
|
|
|
|
<div>
|
2019-07-11 03:58:07 -07:00
|
|
|
<div ref="expression-editor" :style="editorStyle" class="ignore-key-press" @keydown.stop></div>
|
2019-06-23 03:35:23 -07:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
|
|
|
import Vue from 'vue';
|
|
|
|
|
|
|
|
import 'quill/dist/quill.core.css';
|
|
|
|
|
|
|
|
import Quill, { DeltaOperation } from 'quill';
|
|
|
|
// @ts-ignore
|
2021-04-04 08:58:25 -07:00
|
|
|
import AutoFormat from 'quill-autoformat';
|
2019-06-23 03:35:23 -07:00
|
|
|
import {
|
|
|
|
NodeParameterValue,
|
|
|
|
Workflow,
|
|
|
|
} from 'n8n-workflow';
|
|
|
|
|
|
|
|
import {
|
|
|
|
IVariableItemSelected,
|
|
|
|
} from '@/Interface';
|
2021-04-04 08:58:25 -07:00
|
|
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
2019-06-23 03:35:23 -07:00
|
|
|
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
|
|
|
|
|
|
|
import mixins from 'vue-typed-mixins';
|
|
|
|
|
|
|
|
export default mixins(
|
2021-04-04 08:58:25 -07:00
|
|
|
genericHelpers,
|
2019-06-23 03:35:23 -07:00
|
|
|
workflowHelpers,
|
|
|
|
)
|
|
|
|
.extend({
|
|
|
|
name: 'ExpressionInput',
|
|
|
|
props: [
|
|
|
|
'rows',
|
|
|
|
'value',
|
|
|
|
'parameter',
|
|
|
|
'path',
|
|
|
|
'resolvedValue',
|
|
|
|
],
|
|
|
|
data () {
|
|
|
|
return {
|
|
|
|
editor: null as null | Quill,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
editorStyle () {
|
|
|
|
let rows = 1;
|
|
|
|
if (this.rows) {
|
|
|
|
rows = parseInt(this.rows, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
'height': Math.max((rows * 26 + 10), 40) + 'px',
|
|
|
|
};
|
|
|
|
},
|
|
|
|
workflow (): Workflow {
|
|
|
|
return this.getWorkflow();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
value () {
|
|
|
|
if (this.resolvedValue) {
|
|
|
|
// When resolved value gets displayed update the input automatically
|
|
|
|
this.initValue();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
mounted () {
|
|
|
|
const that = this;
|
|
|
|
|
|
|
|
// tslint:disable-next-line
|
|
|
|
const Inline = Quill.import('blots/inline');
|
|
|
|
|
|
|
|
class VariableField extends Inline {
|
|
|
|
static create (value: string) {
|
|
|
|
const node = super.create(value);
|
|
|
|
node.setAttribute('data-value', value);
|
|
|
|
node.setAttribute('class', 'variable');
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static formats (domNode: HTMLElement) {
|
|
|
|
// For the not resolved one the value can be read directly from the dom
|
|
|
|
let variableName = domNode.innerHTML.trim();
|
|
|
|
if (that.resolvedValue) {
|
|
|
|
// For the resolve done it has to get the one from creation.
|
|
|
|
// It will not update on change but because the init runs on every change it does not really matter
|
|
|
|
variableName = domNode.getAttribute('data-value') as string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const newClasses = that.getPlaceholderClasses(variableName);
|
|
|
|
if (domNode.getAttribute('class') !== newClasses) {
|
|
|
|
// Only update when it changed else we get an endless loop!
|
|
|
|
domNode.setAttribute('class', newClasses);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VariableField.blotName = 'variable';
|
|
|
|
VariableField.className = 'variable';
|
|
|
|
VariableField.tagName = 'span';
|
|
|
|
|
|
|
|
Quill.register({
|
|
|
|
'formats/variable': VariableField,
|
|
|
|
});
|
|
|
|
|
|
|
|
AutoFormat.DEFAULTS = {
|
|
|
|
expression: {
|
2019-08-02 07:53:02 -07:00
|
|
|
trigger: /\B[\w\s]/,
|
2019-06-23 03:35:23 -07:00
|
|
|
find: /\{\{[^\s,;:!?}]+\}\}/i,
|
|
|
|
format: 'variable',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
this.editor = new Quill(this.$refs['expression-editor'] as Element, {
|
2021-04-04 08:58:25 -07:00
|
|
|
readOnly: !!this.resolvedValue || this.isReadOnly,
|
2019-06-23 03:35:23 -07:00
|
|
|
modules: {
|
|
|
|
autoformat: {},
|
2020-07-15 02:54:03 -07:00
|
|
|
keyboard: {
|
|
|
|
bindings: {
|
|
|
|
'list autofill': {
|
|
|
|
prefix: /^$/,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-06-23 03:35:23 -07:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.editor.root.addEventListener('blur', (event: Event) => {
|
|
|
|
this.$emit('blur', event);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.initValue();
|
|
|
|
|
|
|
|
if (!this.resolvedValue) {
|
|
|
|
// Only call update when not resolved value gets displayed
|
|
|
|
this.setFocus();
|
|
|
|
this.editor.on('text-change', () => this.update());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
// ------------------------------- EDITOR -------------------------------
|
|
|
|
customizeVariable (variableName: string) {
|
|
|
|
const returnData = {
|
|
|
|
classes: [] as string[],
|
|
|
|
message: variableName as string,
|
|
|
|
};
|
|
|
|
|
|
|
|
let value;
|
|
|
|
try {
|
|
|
|
value = this.resolveExpression(`=${variableName}`);
|
|
|
|
|
|
|
|
if (value !== undefined) {
|
|
|
|
returnData.classes.push('valid');
|
|
|
|
} else {
|
|
|
|
returnData.classes.push('invalid');
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
returnData.classes.push('invalid');
|
|
|
|
}
|
|
|
|
|
|
|
|
return returnData;
|
|
|
|
},
|
|
|
|
// Resolves the given variable. If it is not valid it will return
|
|
|
|
// an error-string.
|
|
|
|
resolveParameterString (variableName: string) {
|
|
|
|
let returnValue;
|
|
|
|
try {
|
|
|
|
returnValue = this.resolveExpression(`=${variableName}`);
|
2020-04-03 10:37:28 -07:00
|
|
|
} catch (error) {
|
|
|
|
return `[invalid (${error.message})]`;
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
if (returnValue === undefined) {
|
2020-04-03 10:37:28 -07:00
|
|
|
return '[not found]';
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return returnValue;
|
|
|
|
},
|
|
|
|
getPlaceholderClasses (variableName: string) {
|
|
|
|
const customizeData = this.customizeVariable(variableName);
|
|
|
|
return 'variable ' + customizeData.classes.join(' ');
|
|
|
|
},
|
|
|
|
getValue () {
|
|
|
|
if (!this.editor) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
const content = this.editor.getContents();
|
|
|
|
if (!content || !content.ops) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
let returnValue = '';
|
|
|
|
|
|
|
|
// Convert the editor operations into a string
|
|
|
|
content.ops.forEach((item: DeltaOperation) => {
|
|
|
|
if (!item.insert) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
returnValue += item.insert;
|
|
|
|
});
|
|
|
|
|
|
|
|
// For some unknown reason does the Quill always return a "\n"
|
|
|
|
// at the end. Remove it here manually
|
|
|
|
return '=' + returnValue.replace(/\s+$/g, '');
|
|
|
|
},
|
|
|
|
setFocus () {
|
|
|
|
// TODO: There is a bug that when opening ExpressionEditor and typing directly it shows the first letter and
|
|
|
|
// then adds the second letter in from of the first on
|
|
|
|
this.editor!.focus();
|
|
|
|
},
|
|
|
|
|
|
|
|
itemSelected (eventData: IVariableItemSelected) {
|
|
|
|
// We can only get the selection if editor is in focus so make
|
|
|
|
// sure it is
|
|
|
|
this.editor!.focus();
|
|
|
|
const selection = this.editor!.getSelection();
|
|
|
|
|
|
|
|
let addIndex = null;
|
|
|
|
if (selection) {
|
|
|
|
addIndex = selection.index;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addIndex) {
|
|
|
|
// If we have a location to add variable to add it there
|
|
|
|
this.editor!.insertText(addIndex, `{{${eventData.variable}}}`, 'variable', true);
|
|
|
|
this.update();
|
|
|
|
} else {
|
|
|
|
// If no position got found add it to end
|
|
|
|
let newValue = this.value;
|
2021-05-31 10:59:45 -07:00
|
|
|
if (newValue === '=' || newValue === '=0') {
|
|
|
|
newValue = `{{${eventData.variable}}}\n`;
|
|
|
|
} else {
|
|
|
|
newValue += ` {{${eventData.variable}}}\n`;
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
2021-05-31 10:59:45 -07:00
|
|
|
|
|
|
|
this.$emit('change', newValue, true);
|
2019-06-23 03:35:23 -07:00
|
|
|
if (!this.resolvedValue) {
|
|
|
|
Vue.nextTick(() => {
|
|
|
|
this.initValue();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
initValue () {
|
|
|
|
if (!this.value) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let currentValue = this.value;
|
|
|
|
|
|
|
|
if (currentValue.charAt(0) === '=') {
|
|
|
|
currentValue = currentValue.slice(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the expression string into a Quill Operations
|
|
|
|
const editorOperations: DeltaOperation[] = [];
|
2019-06-26 22:31:18 -07:00
|
|
|
currentValue.replace(/\{\{(.*?)\}\}/ig, '*%%#_@^$1*%%#_@').split('*%%#_@').forEach((value: string) => {
|
2019-06-23 03:35:23 -07:00
|
|
|
if (!value) {
|
|
|
|
|
|
|
|
} else if (value.charAt(0) === '^') {
|
|
|
|
// Is variable
|
2020-10-26 01:26:07 -07:00
|
|
|
let displayValue = `{{${value.slice(1)}}}` as string | number | boolean | null | undefined;
|
2019-06-23 03:35:23 -07:00
|
|
|
if (this.resolvedValue) {
|
2020-02-08 23:30:09 -08:00
|
|
|
displayValue = [null, undefined].includes(displayValue as null | undefined) ? '' : displayValue;
|
|
|
|
displayValue = this.resolveParameterString((displayValue as string).toString()) as NodeParameterValue;
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
|
2020-02-08 23:30:09 -08:00
|
|
|
displayValue = [null, undefined].includes(displayValue as null | undefined) ? '' : displayValue;
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
editorOperations.push({
|
|
|
|
attributes: {
|
|
|
|
variable: `{{${value.slice(1)}}}`,
|
|
|
|
},
|
2020-02-08 23:30:09 -08:00
|
|
|
insert: (displayValue as string).toString(),
|
2019-06-23 03:35:23 -07:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Is text
|
|
|
|
editorOperations.push({
|
|
|
|
insert: value,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
this.editor!.setContents(editorOperations);
|
|
|
|
},
|
|
|
|
update () {
|
|
|
|
this.$emit('input', this.getValue());
|
|
|
|
this.$emit('change', this.getValue());
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
|
|
.variable-wrapper {
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.variable-value {
|
|
|
|
font-weight: bold;
|
|
|
|
color: #000;
|
|
|
|
background-color: #c0c0c0;
|
|
|
|
padding: 3px;
|
|
|
|
border-radius: 3px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.variable-delete {
|
|
|
|
position: relative;
|
|
|
|
left: -3px;
|
|
|
|
top: -8px;
|
|
|
|
display: none;
|
|
|
|
color: #fff;
|
|
|
|
font-weight: bold;
|
|
|
|
padding: 2px 4px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.variable-wrapper:hover .variable-delete {
|
|
|
|
display: inline;
|
|
|
|
background-color: #AA2200;
|
|
|
|
border-radius: 5px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.variable {
|
|
|
|
font-weight: bold;
|
|
|
|
color: #000;
|
|
|
|
background-color: #c0c0c0;
|
|
|
|
padding: 3px;
|
|
|
|
border-radius: 3px;
|
|
|
|
margin: 0 2px;
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
margin-left: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.invalid {
|
|
|
|
background-color: #e25e5e;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.valid {
|
|
|
|
background-color: #37ac37;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.ql-editor {
|
|
|
|
padding: 0.5em 1em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ql-disabled .ql-editor {
|
|
|
|
border-width: 1px;
|
|
|
|
border: 1px dashed $--custom-expression-text;
|
|
|
|
color: $--custom-expression-text;
|
|
|
|
background-color: $--custom-expression-background;
|
|
|
|
cursor: not-allowed;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ql-disabled .ql-editor .variable {
|
|
|
|
color: #303030;
|
|
|
|
}
|
|
|
|
|
|
|
|
</style>
|