diff --git a/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue b/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue index 9ebb774913..895e79caf2 100644 --- a/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue +++ b/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue @@ -36,11 +36,20 @@ export default mixins(expressionManager).extend({ props: { html: { type: String, + required: true, }, isReadOnly: { type: Boolean, default: false, }, + rows: { + type: Number, + default: -1, + }, + disableExpressionColoring: { + type: Boolean, + default: false, + }, }, data() { return { @@ -72,8 +81,8 @@ export default mixins(expressionManager).extend({ EditorView.updateListener.of((viewUpdate: ViewUpdate) => { if (!viewUpdate.docChanged) return; - highlighter.removeColor(this.editor, this.htmlSegments); - highlighter.addColor(this.editor, this.resolvableSegments); + this.getHighlighter()?.removeColor(this.editor, this.htmlSegments); + this.getHighlighter()?.addColor(this.editor, this.resolvableSegments); this.$emit('valueChanged', this.doc); }), @@ -144,7 +153,31 @@ export default mixins(expressionManager).extend({ return root; }, + isMissingHtmlTags() { + const zerothSection = this.sections.at(0); + + return ( + !zerothSection?.content.trim().startsWith('') + ); + }, + format() { + if (this.sections.length === 1 && this.isMissingHtmlTags()) { + const zerothSection = this.sections.at(0) as Section; + + const formatted = prettier + .format(zerothSection.content, { + parser: 'html', + plugins: [htmlParser], + }) + .trim(); + + return this.editor.dispatch({ + changes: { from: 0, to: this.doc.length, insert: formatted }, + }); + } + const formatted = []; for (const { kind, content } of this.sections) { @@ -185,20 +218,34 @@ export default mixins(expressionManager).extend({ } } + if (formatted.length === 0) return; + this.editor.dispatch({ changes: { from: 0, to: this.doc.length, insert: formatted.join('\n\n') }, }); }, + + getHighlighter() { + if (this.disableExpressionColoring) return; + + return highlighter; + }, }, mounted() { htmlEditorEventBus.$on('format-html', this.format); - const state = EditorState.create({ doc: this.html, extensions: this.extensions }); + let doc = this.html; + + if (this.html === '' && this.rows > 0) { + doc = '\n'.repeat(this.rows - 1); + } + + const state = EditorState.create({ doc, extensions: this.extensions }); this.editor = new EditorView({ parent: this.root(), state }); - highlighter.addColor(this.editor, this.resolvableSegments); + this.getHighlighter()?.addColor(this.editor, this.resolvableSegments); }, destroyed() { diff --git a/packages/editor-ui/src/components/HtmlEditor/theme.ts b/packages/editor-ui/src/components/HtmlEditor/theme.ts index f0460af501..5c2a9845c1 100644 --- a/packages/editor-ui/src/components/HtmlEditor/theme.ts +++ b/packages/editor-ui/src/components/HtmlEditor/theme.ts @@ -18,6 +18,9 @@ export const theme = [ '.cm-cursor, .cm-dropCursor': { borderLeftColor: 'var(--color-code-caret)', }, + '&.cm-editor.cm-focused': { + outline: '0', + }, '&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection': { backgroundColor: 'var(--color-code-selection)', }, @@ -30,6 +33,8 @@ export const theme = [ '.cm-gutters': { backgroundColor: 'var(--color-code-gutterBackground)', color: 'var(--color-code-gutterForeground)', + borderTopLeftRadius: 'var(--border-radius-base)', + borderBottomLeftRadius: 'var(--border-radius-base)', }, '.cm-scroller': { overflow: 'auto', diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index fba845a65f..358e34902d 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -81,9 +81,11 @@ /> @@ -353,8 +355,8 @@ import { workflowHelpers } from '@/mixins/workflowHelpers'; import { hasExpressionMapping, isValueExpression, isResourceLocatorValue } from '@/utils'; import mixins from 'vue-typed-mixins'; -import { CUSTOM_API_CALL_KEY } from '@/constants'; -import { CODE_NODE_TYPE, HTML_NODE_TYPE } from '@/constants'; +import { CUSTOM_API_CALL_KEY, HTML_NODE_TYPE } from '@/constants'; +import { CODE_NODE_TYPE } from '@/constants'; import { PropType } from 'vue'; import { debounceHelper } from '@/mixins/debounce'; import { mapStores } from 'pinia'; diff --git a/packages/editor-ui/src/components/ParameterOptions.vue b/packages/editor-ui/src/components/ParameterOptions.vue index 220b3ac21c..abf0c2e2d0 100644 --- a/packages/editor-ui/src/components/ParameterOptions.vue +++ b/packages/editor-ui/src/components/ParameterOptions.vue @@ -28,9 +28,6 @@ import { NodeParameterValueType } from 'n8n-workflow'; import Vue, { PropType } from 'vue'; import { isValueExpression, isResourceLocatorValue } from '@/utils'; -import { useNDVStore } from '@/stores/ndv'; -import { mapStores } from 'pinia'; -import { HTML_NODE_TYPE } from '@/constants'; export default Vue.extend({ name: 'parameter-options', @@ -54,13 +51,15 @@ export default Vue.extend({ }, }, computed: { - ...mapStores(useNDVStore), isDefault(): boolean { return this.parameter.default === this.value; }, isValueExpression(): boolean { return isValueExpression(this.parameter, this.value); }, + isHtmlEditor(): boolean { + return this.getArgument('editor') === 'htmlEditor'; + }, shouldShowOptions(): boolean { if (this.isReadOnly === true) { return false; @@ -95,13 +94,10 @@ export default Vue.extend({ return !!this.getArgument('loadOptionsMethod') || !!this.getArgument('loadOptions'); }, actions(): Array<{ label: string; value: string; disabled?: boolean }> { - if ( - this.ndvStore.activeNode?.type === HTML_NODE_TYPE && - this.ndvStore.activeNode?.parameters.operation === 'generateHtmlTemplate' - ) { + if (this.isHtmlEditor && !this.isValueExpression) { return [ { - label: 'Format HTML', + label: this.$locale.baseText('parameterInput.formatHtml'), value: 'formatHtml', }, ]; diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index f255b12475..adb35b2260 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -878,6 +878,7 @@ "parameterInput.error": "ERROR", "parameterInput.expression": "Expression", "parameterInput.fixed": "Fixed", + "parameterInput.formatHtml": "Format HTML", "parameterInput.issues": "Issues", "parameterInput.loadingOptions": "Loading options...", "parameterInput.openEditWindow": "Open Edit Window", diff --git a/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts b/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts index ee615d49a7..fc45a8ff4d 100644 --- a/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts +++ b/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts @@ -85,6 +85,7 @@ export class Mailgun implements INodeType { type: 'string', typeOptions: { rows: 5, + editor: 'htmlEditor', }, default: '', description: 'HTML text message of email',