refactor(editor): Replace monaco-editor/prismjs with CodeMirror (#5983)

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
Co-authored-by: Milorad FIlipović <milorad@n8n.io>
Co-authored-by: Alex Grozav <alex@grozav.com>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-04-25 14:57:21 +00:00 committed by GitHub
parent 88724bb056
commit ca4e0df90b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 240 additions and 691 deletions

View file

@ -18,7 +18,7 @@ export class NDV extends BasePage {
outputDisplayMode: () => this.getters.outputPanel().findChildByTestId('ndv-run-data-display-mode').first(),
pinDataButton: () => cy.getByTestId('ndv-pin-data'),
editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'),
pinnedDataEditor: () => this.getters.outputPanel().find('.monaco-editor[role=code]'),
pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller'),
runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'),
savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
outputTableRows: () => this.getters.outputDataContainer().find('table tr'),
@ -77,8 +77,7 @@ export class NDV extends BasePage {
this.getters.editPinnedDataButton().click();
this.getters.pinnedDataEditor().click();
this.getters.pinnedDataEditor().type(`{selectall}{backspace}`);
this.getters.pinnedDataEditor().type(JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}'));
this.getters.pinnedDataEditor().type(`{selectall}{backspace}${JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')}`);
this.actions.savePinnedData();
},

View file

@ -276,6 +276,7 @@
--color-json-highlight: #dcdfea;
--color-code-background: #222020;
--color-code-background-readonly: #323230;
--color-code-foreground: #f8f8f2;
--color-code-caret: #f8f8f0;
--color-code-selection: #312b25;

View file

@ -377,6 +377,7 @@
);
--color-code-background: #ffffff;
--color-code-background-readonly: #f5f2f0;
--color-code-foreground: #4d4d4c;
--color-code-caret: #aeafad;
--color-code-selection: #d6d6d6;

View file

@ -29,6 +29,7 @@
"@codemirror/autocomplete": "^6.4.0",
"@codemirror/commands": "^6.1.0",
"@codemirror/lang-javascript": "^6.1.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.2.1",
"@codemirror/lint": "^6.0.0",
"@codemirror/state": "^6.1.4",
@ -58,13 +59,11 @@
"jsonpath": "^1.1.1",
"lodash-es": "^4.17.21",
"luxon": "^3.3.0",
"monaco-editor": "^0.33.0",
"n8n-design-system": "workspace:*",
"n8n-workflow": "workspace:*",
"normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prettier": "^2.8.3",
"prismjs": "^1.17.1",
"stream-browserify": "^3.0.0",
"timeago.js": "^4.0.2",
"uuid": "^8.3.2",
@ -75,7 +74,6 @@
"vue-i18n": "^8.26.7",
"vue-infinite-loading": "^2.4.5",
"vue-json-pretty": "1.9.3",
"vue-prism-editor": "^0.3.0",
"vue-router": "^3.6.5",
"vue-template-compiler": "^2.7.14",
"vue-typed-mixins": "^0.2.0",
@ -113,7 +111,6 @@
"sass-loader": "^10.1.1",
"string-template-parser": "^1.2.6",
"vite": "4.0.4",
"vite-plugin-monaco-editor": "^1.0.10",
"vitest": "^0.28.5",
"vue-tsc": "^1.0.24"
}

View file

@ -1,281 +0,0 @@
<template>
<el-dialog
visible
append-to-body
:close-on-click-modal="false"
width="80%"
:title="`${$locale.baseText('codeEdit.edit')} ${$locale
.nodeText()
.inputLabelDisplayName(parameter, path)}`"
:before-close="closeDialog"
>
<div class="text-editor-wrapper ignore-key-press">
<code-editor
:value="value"
:autocomplete="loadAutocompleteData"
:readonly="readonly"
@input="$emit('valueChanged', $event)"
/>
</div>
</el-dialog>
</template>
<script lang="ts">
import { genericHelpers } from '@/mixins/genericHelpers';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import mixins from 'vue-typed-mixins';
import type { INodeUi } from '@/Interface';
import type {
IBinaryKeyData,
IDataObject,
INodeExecutionData,
IRunExecutionData,
IWorkflowDataProxyAdditionalKeys,
} from 'n8n-workflow';
import { WorkflowDataProxy } from 'n8n-workflow';
import { PLACEHOLDER_FILLED_AT_EXECUTION_TIME } from '@/constants';
import { CodeEditor } from './forms';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
export default mixins(genericHelpers, workflowHelpers).extend({
name: 'CodeEdit',
components: {
CodeEditor,
},
props: ['codeAutocomplete', 'parameter', 'path', 'type', 'value', 'readonly'],
computed: {
...mapStores(useNDVStore, useRootStore, useWorkflowsStore),
},
methods: {
loadAutocompleteData(): string[] {
if (['function', 'functionItem'].includes(this.codeAutocomplete)) {
const itemIndex = 0;
const inputName = 'main';
const mode = 'manual';
let runIndex = 0;
const executedWorkflow = this.workflowsStore.getWorkflowExecution;
const workflow = this.getCurrentWorkflow();
const activeNode: INodeUi | null = this.ndvStore.activeNode;
const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
const nodeConnection = workflow.getNodeConnectionIndexes(
activeNode!.name,
parentNode[0],
) || {
sourceIndex: 0,
destinationIndex: 0,
};
const executionData = this.workflowsStore.getWorkflowExecution;
let runExecutionData: IRunExecutionData;
if (!executionData || !executionData.data) {
runExecutionData = {
resultData: {
runData: {},
},
};
} else {
runExecutionData = executionData.data;
if (runExecutionData.resultData.runData[activeNode!.name]) {
runIndex = runExecutionData.resultData.runData[activeNode!.name].length - 1;
}
}
const connectionInputData = this.connectionInputData(
parentNode,
activeNode!.name,
inputName,
runIndex,
nodeConnection,
);
const additionalProxyKeys: IWorkflowDataProxyAdditionalKeys = {
$execution: {
id: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
mode: 'test',
resumeUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
},
// deprecated
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
};
const dataProxy = new WorkflowDataProxy(
workflow,
runExecutionData,
runIndex,
itemIndex,
activeNode!.name,
connectionInputData || [],
{},
mode,
this.rootStore.timezone,
additionalProxyKeys,
);
const proxy = dataProxy.getDataProxy();
const autoCompleteItems = [
'function $evaluateExpression(expression: string, itemIndex?: number): any {};',
'function getNodeParameter(parameterName: string, itemIndex: number, fallbackValue?: any): any {};',
'function getWorkflowStaticData(type: string): {};',
'function $item(itemIndex: number, runIndex?: number): {};',
'function $items(nodeName?: string, outputIndex?: number, runIndex?: number): {};',
];
const baseKeys = [
'$env',
'$executionId',
'$mode',
'$parameter',
'$resumeWebhookUrl',
'$vars',
'$workflow',
'$now',
'$today',
'$thisRunIndex',
'DateTime',
'Duration',
'Interval',
];
const functionItemKeys = ['$json', '$binary', '$position', '$thisItem', '$thisItemIndex'];
const additionalKeys: string[] = [];
if (this.codeAutocomplete === 'functionItem') {
additionalKeys.push(...functionItemKeys);
}
if (executedWorkflow && connectionInputData && connectionInputData.length) {
baseKeys.push(...additionalKeys);
} else {
additionalKeys.forEach((key) => {
autoCompleteItems.push(`const ${key} = {}`);
});
}
for (const key of baseKeys) {
autoCompleteItems.push(
`const ${key} = ${JSON.stringify(this.createSimpleRepresentation(proxy[key]))}`,
);
}
// Add the nodes and their simplified data
const nodes: {
[key: string]: INodeExecutionData;
} = {};
for (const [nodeName, node] of Object.entries(workflow.nodes)) {
// To not load to much data create a simple representation.
nodes[nodeName] = {
json: {} as IDataObject,
parameter: this.createSimpleRepresentation(
proxy.$node[nodeName].parameter,
) as IDataObject,
};
try {
nodes[nodeName]!.json = this.createSimpleRepresentation(
proxy.$node[nodeName].json,
) as IDataObject;
nodes[nodeName]!.context = this.createSimpleRepresentation(
proxy.$node[nodeName].context,
) as IDataObject;
nodes[nodeName]!.runIndex = proxy.$node[nodeName].runIndex;
if (Object.keys(proxy.$node[nodeName].binary).length) {
nodes[nodeName]!.binary = this.createSimpleRepresentation(
proxy.$node[nodeName].binary,
) as IBinaryKeyData;
}
} catch (error) {}
}
autoCompleteItems.push(`const $node = ${JSON.stringify(nodes)}`);
autoCompleteItems.push('function $jmespath(jsonDoc: object, query: string): {};');
if (this.codeAutocomplete === 'function') {
if (connectionInputData) {
autoCompleteItems.push(
`const items = ${JSON.stringify(
this.createSimpleRepresentation(connectionInputData),
)}`,
);
} else {
autoCompleteItems.push('const items: {json: {[key: string]: any}}[] = []');
}
} else if (this.codeAutocomplete === 'functionItem') {
if (connectionInputData) {
autoCompleteItems.push('const item = $json');
} else {
autoCompleteItems.push('const item: {[key: string]: any} = {}');
}
}
return autoCompleteItems;
}
return [];
},
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;
},
createSimpleRepresentation(
inputData:
| object
| null
| undefined
| boolean
| string
| number
| boolean[]
| string[]
| number[]
| object[],
):
| object
| null
| undefined
| boolean
| string
| number
| boolean[]
| string[]
| number[]
| object[] {
if (inputData === null || inputData === undefined) {
return inputData;
} else if (typeof inputData === 'string') {
return '';
} else if (typeof inputData === 'boolean') {
return true;
} else if (typeof inputData === 'number') {
return 1;
} else if (Array.isArray(inputData)) {
return inputData.map((value) => this.createSimpleRepresentation(value));
} else if (typeof inputData === 'object') {
const returnData: { [key: string]: object } = {};
Object.keys(inputData).forEach((key) => {
// @ts-ignore
returnData[key] = this.createSimpleRepresentation(inputData[key]);
});
return returnData;
}
return inputData;
},
},
});
</script>
<style scoped>
.text-editor {
min-height: 30rem;
}
</style>

View file

@ -1,11 +1,11 @@
<template>
<div
:class="$style['code-node-editor-container']"
:class="['code-node-editor', $style['code-node-editor-container']]"
@mouseover="onMouseOver"
@mouseout="onMouseOut"
ref="codeNodeEditorContainer"
>
<div ref="codeNodeEditor" class="ph-no-capture"></div>
<div ref="codeNodeEditor" class="code-node-editor-input ph-no-capture"></div>
<n8n-button
v-if="isCloud && (isEditorHovered || isEditorFocused)"
size="small"
@ -19,40 +19,60 @@
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import type { Extension } from '@codemirror/state';
import { Compartment, EditorState } from '@codemirror/state';
import type { ViewUpdate } from '@codemirror/view';
import { EditorView } from '@codemirror/view';
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { baseExtensions } from './baseExtensions';
import { readOnlyEditorExtensions, writableEditorExtensions } from './baseExtensions';
import { linterExtension } from './linter';
import { completerExtension } from './completer';
import { CODE_NODE_EDITOR_THEME } from './theme';
import { codeNodeEditorTheme } from './theme';
import { workflowHelpers } from '@/mixins/workflowHelpers'; // for json field completions
import { ASK_AI_MODAL_KEY, CODE_NODE_TYPE } from '@/constants';
import { codeNodeEditorEventBus } from '@/event-bus';
import { ALL_ITEMS_PLACEHOLDER, EACH_ITEM_PLACEHOLDER } from './constants';
import { mapStores } from 'pinia';
import {
ALL_ITEMS_PLACEHOLDER,
CODE_LANGUAGES,
CODE_MODES,
EACH_ITEM_PLACEHOLDER,
} from './constants';
import { useRootStore } from '@/stores/n8nRootStore';
import Modal from '../Modal.vue';
import { useSettingsStore } from '@/stores/settings';
import type { CodeLanguage, CodeMode } from './types';
const placeholders: Partial<Record<CodeLanguage, Record<CodeMode, string>>> = {
javaScript: {
runOnceForAllItems: ALL_ITEMS_PLACEHOLDER,
runOnceForEachItem: EACH_ITEM_PLACEHOLDER,
},
};
export default mixins(linterExtension, completerExtension, workflowHelpers).extend({
name: 'code-node-editor',
components: { Modal },
props: {
mode: {
type: String,
validator: (value: string): boolean =>
['runOnceForAllItems', 'runOnceForEachItem'].includes(value),
type: String as PropType<CodeMode>,
validator: (value: CodeMode): boolean => CODE_MODES.includes(value),
},
language: {
type: String as PropType<CodeLanguage>,
default: 'javaScript' as CodeLanguage,
validator: (value: CodeLanguage): boolean => CODE_LANGUAGES.includes(value),
},
isReadOnly: {
type: Boolean,
default: false,
},
jsCode: {
value: {
type: String,
},
},
@ -65,9 +85,12 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
};
},
watch: {
mode() {
mode(newMode, previousMode: CodeMode) {
this.reloadLinter();
this.refreshPlaceholder();
if (this.content.trim() === placeholders[this.language]?.[previousMode]) {
this.refreshPlaceholder();
}
},
},
computed: {
@ -81,16 +104,7 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
return this.editor.state.doc.toString();
},
placeholder(): string {
return {
runOnceForAllItems: ALL_ITEMS_PLACEHOLDER,
runOnceForEachItem: EACH_ITEM_PLACEHOLDER,
}[this.mode];
},
previousPlaceholder(): string {
return {
runOnceForAllItems: EACH_ITEM_PLACEHOLDER,
runOnceForEachItem: ALL_ITEMS_PLACEHOLDER,
}[this.mode];
return placeholders[this.language]?.[this.mode] ?? '';
},
},
methods: {
@ -114,25 +128,26 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
reloadLinter() {
if (!this.editor) return;
this.editor.dispatch({
effects: this.linterCompartment.reconfigure(this.linterExtension()),
});
const linter = this.createLinter(this.language);
if (linter) {
this.editor.dispatch({
effects: this.linterCompartment.reconfigure(linter),
});
}
},
refreshPlaceholder() {
if (!this.editor) return;
if (!this.content.trim() || this.content.trim() === this.previousPlaceholder) {
this.editor.dispatch({
changes: { from: 0, to: this.content.length, insert: this.placeholder },
});
}
this.editor.dispatch({
changes: { from: 0, to: this.content.length, insert: this.placeholder },
});
},
highlightLine(line: number | 'final') {
if (!this.editor) return;
if (line === 'final') {
this.editor.dispatch({
selection: { anchor: this.content.trim().length },
selection: { anchor: this.content.length },
});
return;
}
@ -175,45 +190,62 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
},
},
destroyed() {
codeNodeEditorEventBus.off('error-line-number', this.highlightLine);
if (!this.isReadOnly) codeNodeEditorEventBus.off('error-line-number', this.highlightLine);
},
mounted() {
codeNodeEditorEventBus.on('error-line-number', this.highlightLine);
const stateBasedExtensions = [
this.linterCompartment.of(this.linterExtension()),
EditorState.readOnly.of(this.isReadOnly),
EditorView.domEventHandlers({
focus: () => {
this.isEditorFocused = true;
},
blur: () => {
this.isEditorFocused = false;
},
}),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged) return;
this.trackCompletion(viewUpdate);
this.$emit('valueChanged', this.content);
}),
];
if (!this.isReadOnly) codeNodeEditorEventBus.on('error-line-number', this.highlightLine);
// empty on first load, default param value
if (this.jsCode === '') {
if (!this.value) {
this.$emit('valueChanged', this.placeholder);
}
const { isReadOnly, language } = this;
const extensions: Extension[] = [
...readOnlyEditorExtensions,
EditorState.readOnly.of(isReadOnly),
EditorView.editable.of(!isReadOnly),
codeNodeEditorTheme({ isReadOnly }),
];
if (!isReadOnly) {
const linter = this.createLinter(language);
if (linter) {
extensions.push(this.linterCompartment.of(linter));
}
extensions.push(
...writableEditorExtensions,
EditorView.domEventHandlers({
focus: () => {
this.isEditorFocused = true;
},
blur: () => {
this.isEditorFocused = false;
},
}),
EditorView.updateListener.of((viewUpdate) => {
if (!viewUpdate.docChanged) return;
this.trackCompletion(viewUpdate);
this.$emit('valueChanged', this.editor?.state.doc.toString());
}),
);
}
switch (language) {
case 'json':
extensions.push(json());
break;
case 'javaScript':
extensions.push(javascript(), this.autocompletionExtension());
break;
}
const state = EditorState.create({
doc: this.jsCode === '' ? this.placeholder : this.jsCode,
extensions: [
...baseExtensions,
...stateBasedExtensions,
CODE_NODE_EDITOR_THEME,
javascript(),
this.autocompletionExtension(),
],
doc: this.value || this.placeholder,
extensions,
});
this.editor = new EditorView({
@ -227,6 +259,10 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
<style lang="scss" module>
.code-node-editor-container {
position: relative;
& > div {
height: 100%;
}
}
.ask-ai-button {

View file

@ -18,21 +18,26 @@ import {
deleteCharBackward,
} from '@codemirror/commands';
import { lintGutter } from '@codemirror/lint';
import type { Extension } from '@codemirror/state';
import { codeInputHandler } from '@/plugins/codemirror/inputHandlers/code.inputHandler';
export const baseExtensions = [
export const readOnlyEditorExtensions: readonly Extension[] = [
lineNumbers(),
highlightActiveLineGutter(),
EditorView.lineWrapping,
highlightSpecialChars(),
];
export const writableEditorExtensions: readonly Extension[] = [
history(),
foldGutter(),
lintGutter(),
foldGutter(),
codeInputHandler(),
dropCursor(),
indentOnInput(),
bracketMatching(),
highlightActiveLine(),
highlightActiveLineGutter(),
keymap.of([
{ key: 'Enter', run: insertNewlineAndIndent },
{ key: 'Tab', run: acceptCompletion },
@ -42,5 +47,4 @@ export const baseExtensions = [
{ key: 'Backspace', run: deleteCharBackward, shift: deleteCharBackward },
indentWithTab,
]),
EditorView.lineWrapping,
];

View file

@ -51,3 +51,6 @@ $input.item.json.myNewField = 1;
return $input.item;
`.trim();
export const CODE_LANGUAGES = ['javaScript', 'json'] as const;
export const CODE_MODES = ['runOnceForAllItems', 'runOnceForEachItem'] as const;

View file

@ -1,6 +1,7 @@
import Vue from 'vue';
import type { Diagnostic } from '@codemirror/lint';
import { linter as createLinter } from '@codemirror/lint';
import { jsonParseLinter } from '@codemirror/lang-json';
import * as esprima from 'esprima-next';
import {
@ -12,12 +13,18 @@ import { walk } from './utils';
import type { EditorView } from '@codemirror/view';
import type { Node } from 'estree';
import type { CodeNodeEditorMixin, RangeNode } from './types';
import type { CodeLanguage, CodeNodeEditorMixin, RangeNode } from './types';
export const linterExtension = (Vue as CodeNodeEditorMixin).extend({
methods: {
linterExtension() {
return createLinter(this.lintSource, { delay: DEFAULT_LINTER_DELAY_IN_MS });
createLinter(language: CodeLanguage) {
switch (language) {
case 'javaScript':
return createLinter(this.lintSource, { delay: DEFAULT_LINTER_DELAY_IN_MS });
case 'json':
return createLinter(jsonParseLinter());
}
return undefined;
},
lintSource(editorView: EditorView): Diagnostic[] {

View file

@ -29,7 +29,11 @@ const BASE_STYLING = {
const cssStyleDeclaration = getComputedStyle(document.documentElement);
export const CODE_NODE_EDITOR_THEME = [
interface ThemeSettings {
isReadOnly?: boolean;
}
export const codeNodeEditorTheme = ({ isReadOnly }: ThemeSettings) => [
EditorView.theme({
'&': {
'font-size': BASE_STYLING.fontSize,
@ -37,6 +41,7 @@ export const CODE_NODE_EDITOR_THEME = [
borderRadius: cssStyleDeclaration.getPropertyValue('--border-radius-base'),
backgroundColor: 'var(--color-code-background)',
color: 'var(--color-code-foreground)',
height: '100%',
},
'.cm-content': {
fontFamily: BASE_STYLING.fontFamily,
@ -48,6 +53,9 @@ export const CODE_NODE_EDITOR_THEME = [
'&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection': {
backgroundColor: 'var(--color-code-selection)',
},
'&.cm-editor': {
...(isReadOnly ? { backgroundColor: 'var(--color-code-background-readonly)' } : {}),
},
'&.cm-editor.cm-focused': {
outline: 'none',
borderColor: 'var(--color-secondary)',
@ -59,7 +67,9 @@ export const CODE_NODE_EDITOR_THEME = [
backgroundColor: 'var(--color-code-lineHighlight)',
},
'.cm-gutters': {
backgroundColor: 'var(--color-code-gutterBackground)',
backgroundColor: isReadOnly
? 'var(--color-code-background-readonly)'
: 'var(--color-code-gutterBackground)',
color: 'var(--color-code-gutterForeground)',
borderRadius: 'var(--border-radius-base)',
},
@ -69,7 +79,8 @@ export const CODE_NODE_EDITOR_THEME = [
},
'.cm-scroller': {
overflow: 'auto',
maxHeight: BASE_STYLING.maxHeight,
maxHeight: '100%',
...(isReadOnly ? {} : { minHeight: '10em' }),
},
'.cm-diagnosticAction': {
backgroundColor: BASE_STYLING.diagnosticButton.backgroundColor,

View file

@ -2,6 +2,7 @@ import type { EditorView } from '@codemirror/view';
import type { I18nClass } from '@/plugins/i18n';
import type { Workflow } from 'n8n-workflow';
import type { Node } from 'estree';
import type { CODE_LANGUAGES, CODE_MODES } from './constants';
export type CodeNodeEditorMixin = Vue.VueConstructor<
Vue & {
@ -13,3 +14,6 @@ export type CodeNodeEditorMixin = Vue.VueConstructor<
>;
export type RangeNode = Node & { range: [number, number] };
export type CodeLanguage = (typeof CODE_LANGUAGES)[number];
export type CodeMode = (typeof CODE_MODES)[number];

View file

@ -117,7 +117,7 @@
}}</n8n-text>
</template>
<template #recovered-artifical-output-data>
<template #recovered-artificial-output-data>
<div :class="$style.recoveredOutputData">
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('executionDetails.executionFailed.recoveredNodeTitle')

View file

@ -436,6 +436,10 @@ export default mixins(debounceHelper).extend({
visibility: hidden;
}
.double-width {
left: 90%;
}
.dragButtonContainer {
position: absolute;
top: -12px;

View file

@ -41,6 +41,7 @@
:hideInputAndOutput="activeNodeType === null"
:position="isTriggerNode && !showTriggerPanel ? 0 : undefined"
:isDraggable="!isTriggerNode"
:hasDoubleWidth="activeNodeType?.parameterPane === 'wide'"
:nodeType="activeNodeType"
@close="close"
@init="onPanelsInit"

View file

@ -962,7 +962,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
.node-parameters-wrapper {
height: 100%;
overflow-y: auto;
padding: 0 20px 200px 20px;
padding: 0 20px;
}
&.dragging {

View file

@ -80,7 +80,7 @@
</n8n-text>
</template>
<template #recovered-artifical-output-data>
<template #recovered-artificial-output-data>
<div :class="$style.recoveredOutputData">
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('executionDetails.executionFailed.recoveredNodeTitle')

View file

@ -51,17 +51,28 @@
remoteParameterOptionsLoadingIssues !== null
"
>
<code-edit
<el-dialog
v-if="codeEditDialogVisible"
:value="value"
:parameter="parameter"
:type="editorType"
:codeAutocomplete="codeAutocomplete"
:path="path"
:readonly="isReadOnly"
@closeDialog="closeCodeEditDialog"
@valueChanged="expressionUpdated"
></code-edit>
visible
append-to-body
:close-on-click-modal="false"
width="80%"
:title="`${$locale.baseText('codeEdit.edit')} ${$locale
.nodeText()
.inputLabelDisplayName(parameter, path)}`"
:before-close="closeCodeEditDialog"
>
<div class="ignore-key-press">
<code-node-editor
:value="value"
:defaultValue="parameter.default"
:language="editorLanguage"
:isReadOnly="isReadOnly"
@valueChanged="expressionUpdated"
/>
</div>
</el-dialog>
<text-edit
:dialogVisible="textEditDialogVisible"
:value="value"
@ -73,15 +84,17 @@
></text-edit>
<code-node-editor
v-if="getArgument('editor') === 'codeNodeEditor' && isCodeNode(node)"
v-if="editorType === 'codeNodeEditor' && isCodeNode(node)"
:mode="node.parameters.mode"
:jsCode="node.parameters.jsCode"
:value="node.parameters.jsCode"
:defaultValue="parameter.default"
:language="editorLanguage"
:isReadOnly="isReadOnly"
@valueChanged="valueChangedDebounced"
/>
<html-editor
v-else-if="getArgument('editor') === 'htmlEditor'"
v-else-if="editorType === 'htmlEditor'"
:html="node.parameters.html"
:isReadOnly="isReadOnly"
:rows="getArgument('rows')"
@ -91,17 +104,16 @@
/>
<div
v-else-if="isEditor === true"
class="code-edit clickable ph-no-capture"
v-else-if="editorType"
class="readonly-code clickable ph-no-capture"
@click="displayEditDialog()"
>
<prism-editor
<code-node-editor
v-if="!codeEditDialogVisible"
:lineNumbers="true"
:readonly="true"
:code="displayValue"
language="js"
></prism-editor>
:value="value"
:language="editorLanguage"
:isReadOnly="true"
/>
</div>
<n8n-input
@ -338,10 +350,11 @@ import type {
INodeProperties,
INodePropertyCollection,
NodeParameterValueType,
EditorType,
CodeNodeEditorLanguage,
} from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
import CodeEdit from '@/components/CodeEdit.vue';
import CredentialsSelect from '@/components/CredentialsSelect.vue';
import ImportParameter from '@/components/ImportParameter.vue';
import ExpressionEdit from '@/components/ExpressionEdit.vue';
@ -351,8 +364,6 @@ import ParameterOptions from '@/components/ParameterOptions.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import ResourceLocator from '@/components/ResourceLocator/ResourceLocator.vue';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
// @ts-ignore
import PrismEditor from 'vue-prism-editor';
import TextEdit from '@/components/TextEdit.vue';
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue';
@ -385,14 +396,12 @@ export default mixins(
).extend({
name: 'parameter-input',
components: {
CodeEdit,
CodeNodeEditor,
HtmlEditor,
ExpressionEdit,
ExpressionParameterInput,
NodeCredentials,
CredentialsSelect,
PrismEditor,
ScopesNotice,
ParameterOptions,
ParameterIssues,
@ -554,8 +563,8 @@ export default mixins(
return null;
}
},
node(): INodeUi | null {
return this.ndvStore.activeNode;
node(): INodeUi {
return this.ndvStore.activeNode!;
},
displayTitle(): string {
const interpolation = { interpolate: { shortPath: this.shortPath } };
@ -636,7 +645,7 @@ export default mixins(
return 'textarea';
}
if (this.parameter.typeOptions && this.parameter.typeOptions.editor === 'code') {
if (this.editorType === 'code') {
return 'textarea';
}
@ -719,11 +728,12 @@ export default mixins(
return [];
},
isEditor(): boolean {
return ['code', 'json'].includes(this.editorType);
editorType(): EditorType {
return this.getArgument('editor') as EditorType;
},
editorType(): string {
return this.getArgument('editor') as string;
editorLanguage(): CodeNodeEditorLanguage {
if (this.editorType === 'json' || this.parameter.type === 'json') return 'json';
return 'javaScript';
},
parameterOptions():
| Array<INodePropertyOptions | INodeProperties | INodePropertyCollection>
@ -907,22 +917,14 @@ export default mixins(
this.textEditDialogVisible = false;
},
displayEditDialog() {
if (this.isEditor) {
if (this.editorType) {
this.codeEditDialogVisible = true;
} else {
this.textEditDialogVisible = true;
}
},
getArgument(argumentName: string): string | number | boolean | undefined {
if (this.parameter.typeOptions === undefined) {
return undefined;
}
if (this.parameter.typeOptions[argumentName] === undefined) {
return undefined;
}
return this.parameter.typeOptions[argumentName];
return this.parameter.typeOptions?.[argumentName];
},
expressionUpdated(value: string) {
const val: NodeParameterValueType = this.isResourceLocatorParameter
@ -1167,32 +1169,6 @@ export default mixins(
},
{ deep: true, immediate: true },
);
// Reload function on change element from
// displayOptions.typeOptions.reloadOnChange parameters
if (this.parameter.typeOptions && this.parameter.typeOptions.reloadOnChange) {
// Get all parameter in reloadOnChange property
// This reload when parameters in reloadOnChange is updated
const parametersOnChange: string[] = this.parameter.typeOptions.reloadOnChange;
for (let i = 0; i < parametersOnChange.length; i++) {
const parameter = parametersOnChange[i] as string;
if (parameter in this.node.parameters) {
this.$watch(
() => {
if (this.node && this.node.parameters && this.node.parameters[parameter]) {
return this.node.parameters![parameter];
} else {
return null;
}
},
() => {
this.loadRemoteParameterOptions();
},
{ deep: true, immediate: true },
);
}
}
}
}
this.$externalHooks().run('parameterInput.mount', {
@ -1204,7 +1180,7 @@ export default mixins(
</script>
<style scoped lang="scss">
.code-edit {
.readonly-code {
font-size: var(--font-size-xs);
}

View file

@ -70,11 +70,7 @@ export default defineComponent({
return false;
}
if (
this.parameter.typeOptions &&
this.parameter.typeOptions.editor &&
this.parameter.typeOptions.editor === 'codeNodeEditor'
) {
if (this.parameter.typeOptions?.editor === 'codeNodeEditor') {
return false;
}

View file

@ -1,5 +1,5 @@
<template>
<div :class="$style.container">
<div :class="['run-data', $style.container]">
<n8n-callout
v-if="canPinData && hasPinData && !editMode.enabled && !isProductionExecutionPreview"
theme="secondary"
@ -190,11 +190,10 @@
<div v-else-if="editMode.enabled" :class="$style.editMode">
<div :class="[$style.editModeBody, 'ignore-key-press']">
<code-editor
<code-node-editor
:value="editMode.value"
:options="{ scrollBeyondLastLine: false }"
type="json"
@input="ndvStore.setOutputPanelEditModeValue($event)"
language="json"
@valueChanged="ndvStore.setOutputPanelEditModeValue($event)"
/>
</div>
<div :class="$style.editModeFooter">
@ -228,7 +227,7 @@
</div>
<div v-else-if="hasNodeRun && isArtificialRecoveredEventItem" :class="$style.center">
<slot name="recovered-artifical-output-data"></slot>
<slot name="recovered-artificial-output-data"></slot>
</div>
<div v-else-if="hasNodeRun && hasRunError" :class="$style.stretchVertically">
@ -501,7 +500,7 @@ import { externalHooks } from '@/mixins/externalHooks';
import { genericHelpers } from '@/mixins/genericHelpers';
import { nodeHelpers } from '@/mixins/nodeHelpers';
import { pinData } from '@/mixins/pinData';
import { CodeEditor } from '@/components/forms';
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
import { dataPinningEventBus } from '@/event-bus';
import { clearJsonKey, executionDataToJson, stringSizeInBytes } from '@/utils';
import { isEmpty } from '@/utils';
@ -525,7 +524,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers, pinData).exten
BinaryDataDisplay,
NodeErrorView,
WarningTooltip,
CodeEditor,
CodeNodeEditor,
RunDataTable,
RunDataJson,
RunDataSchema,
@ -1571,28 +1570,30 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers, pinData).exten
}
.editMode {
height: calc(100% - var(--spacing-s));
height: 100%;
max-height: calc(100% - var(--spacing-3xl));
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
justify-content: stretch;
padding-left: var(--spacing-s);
padding-right: var(--spacing-s);
}
.editModeBody {
flex: 1 1 auto;
max-height: 100%;
width: 100%;
height: 100%;
overflow: hidden;
overflow: auto;
}
.editModeFooter {
flex: 0 1 auto;
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
padding-top: var(--spacing-s);
padding-bottom: var(--spacing-s);
}
.editModeFooterInfotip {
@ -1617,3 +1618,11 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers, pinData).exten
border-bottom-left-radius: 0;
}
</style>
<style lang="scss" scoped>
.run-data {
.code-node-editor {
height: 100%;
}
}
</style>

View file

@ -1,137 +0,0 @@
<template>
<div ref="code" class="text-editor ph-no-capture" @keydown.stop />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as monaco from 'monaco-editor';
export default defineComponent({
props: {
type: {
type: String,
default: 'code',
},
readonly: {
type: Boolean,
default: false,
},
value: {
type: String,
default: '',
},
autocomplete: {
type: Function,
},
options: {
type: Object,
default: () => ({}),
},
},
data() {
return {
monacoInstance: null as monaco.editor.IStandaloneCodeEditor | null,
monacoLibrary: null as monaco.IDisposable | null,
};
},
methods: {
loadEditor() {
if (!this.$refs.code) return;
this.monacoInstance = monaco.editor.create(this.$refs.code as HTMLElement, {
automaticLayout: true,
value: this.value,
language: this.type === 'code' ? 'javascript' : 'json',
tabSize: 2,
wordBasedSuggestions: false,
readOnly: this.readonly,
padding: {
top: 16,
},
minimap: {
enabled: false,
},
...this.options,
});
this.monacoInstance.onDidChangeModelContent(() => {
const model = this.monacoInstance!.getModel();
if (model) {
this.$emit('input', model.getValue());
}
});
const darkModeBetaEnabled =
document.body.classList.contains('theme-dark-beta') &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
monaco.editor.defineTheme('n8nCustomTheme', {
base: darkModeBetaEnabled ? 'vs-dark' : 'vs',
inherit: true,
rules: [],
colors: {},
});
monaco.editor.setTheme('n8nCustomTheme');
if (this.type === 'code') {
// As wordBasedSuggestions: false does not have any effect does it however seem
// to remove all all suggestions from the editor if I do this
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
allowNonTsExtensions: true,
});
if (this.autocomplete) {
this.monacoLibrary = monaco.languages.typescript.javascriptDefaults.addExtraLib(
this.autocomplete().join('\n'),
);
}
} else if (this.type === 'json') {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
});
}
},
handleResize() {
if (this.monacoInstance) {
// Workaround to force Monaco to recompute its boundaries
this.monacoInstance.layout({} as unknown as undefined);
}
},
},
mounted() {
setTimeout(this.loadEditor);
window.addEventListener('resize', this.handleResize);
},
destroyed() {
if (this.monacoLibrary) {
this.monacoLibrary.dispose();
}
window.removeEventListener('resize', this.handleResize);
},
});
</script>
<style lang="scss" scoped>
.text-editor {
width: 100%;
height: 100%;
flex: 1 1 auto;
border: 1px solid var(--color-foreground-base);
border-radius: var(--border-radius-base);
}
::v-deep {
.monaco-editor {
.slider {
border-radius: var(--border-radius-base);
}
&,
&-background,
.inputarea.ime-input,
.margin {
border-radius: var(--border-radius-base);
}
}
}
</style>

View file

@ -1 +0,0 @@
export { default as CodeEditor } from './CodeEditor.vue';

View file

@ -3,9 +3,6 @@
import Vue from 'vue';
import './plugins';
import 'prismjs';
import 'prismjs/themes/prism.css';
import 'vue-prism-editor/dist/VuePrismEditor.css';
import 'vue-json-pretty/lib/styles.css';
import '@jsplumb/browser-ui/css/jsplumbtoolkit.css';
import 'n8n-design-system/css/index.scss';

View file

@ -1,6 +1,5 @@
import vue from '@vitejs/plugin-vue2';
import legacy from '@vitejs/plugin-legacy';
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
import path, { resolve } from 'path';
import { defineConfig, mergeConfig } from 'vite';
import { defineConfig as defineVitestConfig } from 'vitest/config';
@ -73,24 +72,11 @@ const alias = [
},
];
// https://github.com/vitest-dev/vitest/discussions/1806
if (NODE_ENV === 'test') {
alias.push({
find: /^monaco-editor$/,
replacement: __dirname + '/node_modules/monaco-editor/esm/vs/editor/editor.api',
});
}
const plugins = [
vue(),
legacy({
targets: ['>1%', 'last 3 versions', 'not dead'],
}),
monacoEditorPlugin({
publicPath: 'assets/monaco-editor',
customDistPath: (root: string, buildOutDir: string, base: string) =>
`${root}/${buildOutDir}/assets/monaco-editor`,
}),
];
const { SENTRY_AUTH_TOKEN: authToken, RELEASE: release } = process.env;

View file

@ -1019,7 +1019,8 @@ export type NodePropertyTypes =
export type CodeAutocompleteTypes = 'function' | 'functionItem';
export type EditorTypes = 'code' | 'codeNodeEditor' | 'htmlEditor' | 'json';
export type EditorType = 'code' | 'codeNodeEditor' | 'htmlEditor' | 'json';
export type CodeNodeEditorLanguage = 'javaScript' | 'json'; //| 'python' | 'sql';
export interface ILoadOptions {
routing?: {
@ -1032,7 +1033,8 @@ export interface ILoadOptions {
export interface INodePropertyTypeOptions {
alwaysOpenEditWindow?: boolean; // Supported by: json
codeAutocomplete?: CodeAutocompleteTypes; // Supported by: string
editor?: EditorTypes; // Supported by: string
editor?: EditorType; // Supported by: string
editorLanguage?: CodeNodeEditorLanguage; // Supported by: string in combination with editor: codeNodeEditor
loadOptionsDependsOn?: string[]; // Supported by: options
loadOptionsMethod?: string; // Supported by: options
loadOptions?: ILoadOptions; // Supported by: options

View file

@ -841,6 +841,9 @@ importers:
'@codemirror/lang-javascript':
specifier: ^6.1.2
version: 6.1.2
'@codemirror/lang-json':
specifier: ^6.0.1
version: 6.0.1
'@codemirror/language':
specifier: ^6.2.1
version: 6.2.1
@ -928,9 +931,6 @@ importers:
luxon:
specifier: ^3.3.0
version: 3.3.0
monaco-editor:
specifier: ^0.33.0
version: 0.33.0
n8n-design-system:
specifier: workspace:*
version: link:../design-system
@ -946,9 +946,6 @@ importers:
prettier:
specifier: ^2.8.3
version: 2.8.3
prismjs:
specifier: ^1.17.1
version: 1.17.1
stream-browserify:
specifier: ^3.0.0
version: 3.0.0
@ -979,9 +976,6 @@ importers:
vue-json-pretty:
specifier: 1.9.3
version: 1.9.3
vue-prism-editor:
specifier: ^0.3.0
version: 0.3.0
vue-router:
specifier: ^3.6.5
version: 3.6.5(vue@2.7.14)
@ -1088,9 +1082,6 @@ importers:
vite:
specifier: 4.0.4
version: 4.0.4(@types/node@16.18.12)(sass@1.55.0)(terser@5.16.1)
vite-plugin-monaco-editor:
specifier: ^1.0.10
version: 1.0.10(monaco-editor@0.33.0)
vitest:
specifier: ^0.28.5
version: 0.28.5(sass@1.55.0)(terser@5.16.1)
@ -3369,6 +3360,13 @@ packages:
'@lezer/javascript': 1.0.2
dev: false
/@codemirror/lang-json@6.0.1:
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
dependencies:
'@codemirror/language': 6.2.1
'@lezer/json': 1.0.0
dev: false
/@codemirror/language@6.2.1:
resolution: {integrity: sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==}
dependencies:
@ -4232,6 +4230,13 @@ packages:
'@lezer/lr': 1.2.3
dev: false
/@lezer/json@1.0.0:
resolution: {integrity: sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==}
dependencies:
'@lezer/highlight': 1.1.1
'@lezer/lr': 1.2.3
dev: false
/@lezer/lr@1.2.3:
resolution: {integrity: sha512-qpB7rBzH8f6Mzjv2AVZRahcm+2Cf7nbIH++uXbvVOL1yIRvVWQ3HAM/saeBLCyz/togB7LGo76qdJYL1uKQlqA==}
dependencies:
@ -9373,15 +9378,6 @@ packages:
safe-buffer: 5.2.1
dev: false
/clipboard@2.0.11:
resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
dependencies:
good-listener: 1.2.2
select: 1.1.2
tiny-emitter: 2.1.0
dev: false
optional: true
/cliui@3.2.0:
resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==}
dependencies:
@ -9615,18 +9611,10 @@ packages:
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
dev: true
/component-props@1.1.1:
resolution: {integrity: sha512-69pIRJs9fCCHRqCz3390YF2LV1Lu6iEMZ5zuVqqUn+G20V9BNXlMs0cWawWeW9g4Ynmg29JmkG6R7/lUJoGd1Q==}
dev: false
/component-type@1.2.1:
resolution: {integrity: sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg==}
dev: false
/component-xor@0.0.4:
resolution: {integrity: sha512-ZIt6sla8gfo+AFVRZoZOertcnD5LJaY2T9CKE2j13NJxQt/mUafD69Bl7/Y4AnpI2LGjiXH7cOfJDx/n2G9edA==}
dev: false
/compressible@2.0.18:
resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
engines: {node: '>= 0.6'}
@ -10540,11 +10528,6 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
/delegate@3.2.0:
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
dev: false
optional: true
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
@ -10691,13 +10674,6 @@ packages:
resolution: {integrity: sha512-jNCX+uNJ3v38BKvPbpki6j5ItVlnSqVV6vDWGS6rExzCMjsc39frLjm1n91o6YaKK6AZl0wLloItW6C6mr61BQ==}
dev: true
/dom-iterator@1.0.0:
resolution: {integrity: sha512-7dsMOQI07EMU98gQM8NSB3GsAiIeBYIPKpnxR3c9xOvdvBjChAcOM0iJ222I3p5xyiZO9e5oggkNaCusuTdYig==}
dependencies:
component-props: 1.1.1
component-xor: 0.0.4
dev: false
/dom-serializer@0.2.2:
resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
dependencies:
@ -12824,13 +12800,6 @@ packages:
- supports-color
dev: false
/good-listener@1.2.2:
resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
dependencies:
delegate: 3.2.0
dev: false
optional: true
/google-timezones-json@1.1.0:
resolution: {integrity: sha512-6BmBx9gJVALV2jsfMks8PwmkWT5ip3+bmMyTgXu4PY+G8nKjHi61yrL7rSXpMYRsIzUXhVKpj+MnjhnwG9nung==}
dev: false
@ -16126,9 +16095,6 @@ packages:
/moment@2.29.4:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
/monaco-editor@0.33.0:
resolution: {integrity: sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==}
/mongodb-connection-string-url@2.5.4:
resolution: {integrity: sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w==}
dependencies:
@ -17812,12 +17778,6 @@ packages:
hasBin: true
dev: false
/prismjs@1.17.1:
resolution: {integrity: sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==}
optionalDependencies:
clipboard: 2.0.11
dev: false
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
@ -19075,11 +19035,6 @@ packages:
parseley: 0.7.0
dev: false
/select@1.1.2:
resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
dev: false
optional: true
/semver-greatest-satisfied-range@1.1.0:
resolution: {integrity: sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==}
engines: {node: '>= 0.10'}
@ -20370,11 +20325,6 @@ packages:
resolution: {integrity: sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==}
dev: false
/tiny-emitter@2.1.0:
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
dev: false
optional: true
/tiny-glob@0.2.9:
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
dependencies:
@ -21458,14 +21408,6 @@ packages:
- terser
dev: true
/vite-plugin-monaco-editor@1.0.10(monaco-editor@0.33.0):
resolution: {integrity: sha512-7yTAFIE0SefjCmfnjrvXOl53wkxeSASc/ZIcB5tZeEK3vAmHhveV8y3f90Vp8b+PYdbUipjqf91mbFbSENkpcw==}
peerDependencies:
monaco-editor: 0.29.x
dependencies:
monaco-editor: 0.33.0
dev: true
/vite@4.0.4(@types/node@16.18.12)(sass@1.55.0)(terser@5.16.1):
resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -21840,14 +21782,6 @@ packages:
- whiskers
dev: true
/vue-prism-editor@0.3.0:
resolution: {integrity: sha512-yNSuwql/xHMJrWghn/OhZ5WPBKdhx7FkvFjgq2uDm99jHSJhuGwhcgPyuoGzpm6w8DRDzi85lgerKCu8DTDWWg==}
dependencies:
dom-iterator: 1.0.0
escape-html: 1.0.3
unescape: 1.0.1
dev: false
/vue-property-decorator@9.1.2(vue-class-component@7.2.6)(vue@2.7.14):
resolution: {integrity: sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==}
peerDependencies: